-
Notifications
You must be signed in to change notification settings - Fork 484
Resolve concurrency issues #834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
b2ac745
6b6dd2d
66f6b7d
5296d46
5294b23
1ba7a9c
6ea11c2
0da185b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,19 +31,21 @@ class Settings: | |
| _pyfile_data = None | ||
| _mod_settings = dict() | ||
|
|
||
| def __init__(self, settings=None): | ||
| if settings: | ||
| self._updateall(settings.items()) | ||
| else: | ||
| def __init__(self, **kwargs): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Gallaecio @alex-triphonov Something like: I know the code won't be nice, but I think in that way we could keep the backward compatibility until the next major version. (You should do the same for Maybe I'm saying something stupid, sorry guys, I didn't have too much time to play with the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No this is not possible- settings are still being used, but in kwargs for more convenience, the only way to separate behaviours is to add some new kwarg like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me it’s mostly a matter of API. The current change would require to postpone this to dateparser 2.x, as it is backward incompatible. If we can figure a backward compatible implementation, this can go on the next dateparser version. |
||
| if not kwargs.get('settings'): | ||
| self._updateall(self._get_settings_from_pyfile().items()) | ||
|
|
||
| elif len(self.__dict__) == 1: | ||
Gallaecio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self._updateall(kwargs['settings'].items()) | ||
|
|
||
| @classmethod | ||
| def get_key(cls, settings=None): | ||
| if not settings: | ||
| return 'default' | ||
| def get_key(cls, **kwargs): | ||
| if kwargs: | ||
| keys = sorted('{}-{}'.format(key, val) for key, val in kwargs.pop('settings').items()) | ||
| keys.extend(sorted('{}-{}'.format(key, val) for key, val in kwargs.items() if val)) | ||
| return hashlib.md5(''.join(sorted(keys)).encode('utf-8')).hexdigest() | ||
|
|
||
| keys = sorted(['%s-%s' % (key, str(settings[key])) for key in settings]) | ||
| return hashlib.md5(''.join(keys).encode('utf-8')).hexdigest() | ||
| return 'default' | ||
|
|
||
| @classmethod | ||
| def _get_settings_from_pyfile(cls): | ||
|
|
@@ -57,18 +59,20 @@ def _updateall(self, iterable): | |
| setattr(self, key, value) | ||
|
|
||
| def replace(self, mod_settings=None, **kwds): | ||
Gallaecio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for k, v in kwds.items(): | ||
| _settings = kwds.get('settings', {}).copy() | ||
| for k, v in _settings.items(): | ||
| if v is None: | ||
| raise TypeError('Invalid {{"{}": {}}}'.format(k, v)) | ||
|
|
||
| for x in self._get_settings_from_pyfile().keys(): | ||
| kwds.setdefault(x, getattr(self, x)) | ||
| for key in self._get_settings_from_pyfile().keys(): | ||
| _settings.setdefault(key, getattr(self, key)) | ||
|
|
||
| kwds['_default'] = False | ||
| _settings['_default'] = False | ||
| if mod_settings: | ||
| kwds['_mod_settings'] = mod_settings | ||
| _settings['_mod_settings'] = mod_settings | ||
|
|
||
| return self.__class__(settings=kwds) | ||
| kwds['settings'] = _settings | ||
| return self.__class__(**kwds) | ||
|
|
||
|
|
||
| settings = Settings() | ||
|
|
@@ -77,11 +81,15 @@ def replace(self, mod_settings=None, **kwds): | |
| def apply_settings(f): | ||
| @wraps(f) | ||
| def wrapper(*args, **kwargs): | ||
| mod_settings = kwargs.get('settings') | ||
| kwargs['settings'] = mod_settings or settings | ||
| mod_settings = kwargs.get('settings', {}) | ||
| if mod_settings is None: | ||
| kwargs['settings'] = mod_settings = {} | ||
Gallaecio marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if isinstance(kwargs['settings'], dict): | ||
| kwargs['settings'] = settings.replace(mod_settings=mod_settings, **kwargs['settings']) | ||
| if kwargs: | ||
| if isinstance(mod_settings, dict): | ||
| kwargs['settings'] = settings.replace(mod_settings=mod_settings.copy(), **kwargs) | ||
| else: | ||
| kwargs['settings'] = settings | ||
|
|
||
| if not isinstance(kwargs['settings'], Settings): | ||
| raise TypeError("settings can only be either dict or instance of Settings class") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import concurrent.futures | ||
| import random | ||
| from datetime import datetime | ||
|
|
||
| import dateparser | ||
| from tests import BaseTestCase | ||
|
|
||
| RELATIVE = {'RELATIVE_BASE': datetime(2014, 9, 15, 10, 30)} | ||
|
|
||
| TEST_DATA = [ | ||
| {'ds': 'Tue May 07, 2018 10:55 PM', 'expected': datetime(2018, 5, 7, 22, 55), 'loc': 'en'}, | ||
| {'ds': '2018-10-07T22:55:01', 'expected': datetime(2018, 10, 7, 22, 55, 1), 'loc': 'en'}, | ||
| {'ds': '2018-Oct-11', 'expected': datetime(2018, 10, 11, 0, 0), 'loc': 'en'}, | ||
| {'ds': '12.04.2018', 'expected': datetime(2018, 12, 4, 0, 0), 'loc': 'en'}, | ||
| {'ds': '12-10-2018 20:13', 'expected': datetime(2018, 12, 10, 20, 13), 'loc': 'en'}, | ||
| {'ds': '03.04.2019', 'expected': datetime(2019, 4, 3, 0, 0), 'loc': 'en-150'}, | ||
| {'ds': 'on Tue October 7, 2019 04:55 PM', 'expected': datetime(2019, 10, 7, 16, 55), 'loc': 'en-150'}, | ||
| {'ds': '2019Oct8', 'expected': datetime(2019, 10, 8, 0, 0), 'loc': 'en-150'}, | ||
| {'ds': '07.03.2020 - 11:13', 'expected': datetime(2020, 3, 7, 11, 13), 'loc': 'ru'}, | ||
| {'ds': '9 Авг. 2020 17:11:01', 'expected': datetime(2020, 8, 9, 17, 11, 1), 'loc': 'ru'}, | ||
| {'ds': '07.01.2020', 'expected': datetime(2020, 1, 7, 0, 0), 'loc': 'ru'}, | ||
| {'ds': 'yesterday 11:00', 'expected': datetime(2014, 9, 14, 11), 'loc': 'en', 'extra': RELATIVE}, | ||
| {'ds': '13 days ago', 'expected': datetime(2014, 9, 2, 10, 30), 'loc': 'en', 'extra': RELATIVE}, | ||
| ] * 180 | ||
|
|
||
| random.shuffle(TEST_DATA) | ||
|
|
||
|
|
||
| class TestConcurrency(BaseTestCase): | ||
|
|
||
| def test_concurrency(self): | ||
| with concurrent.futures.ThreadPoolExecutor() as executor: | ||
|
|
||
| results = list(executor.map(self.concurrency_test, TEST_DATA)) | ||
| results_with_error = [(r['ds'], r['error']) for r in results if r['error']] | ||
| msg = '{}Threads failed with errors:\n{}' | ||
| self.assertEqual([], results_with_error, | ||
| msg.format(len(results_with_error), set(results_with_error))) | ||
|
|
||
| wrong_results = [str(r) for r in results if (r['expected'] != r['date'])] | ||
| msg = '{} Threads returned wrong date time:\n{}' | ||
| self.assertEqual([], wrong_results, | ||
| msg.format(len(wrong_results), '\n'.join(wrong_results))) | ||
|
|
||
| @staticmethod | ||
| def concurrency_test(data_for_test): | ||
| try: | ||
| date_string = data_for_test['ds'] | ||
| date = dateparser.parse(date_string, locales=[data_for_test['loc']], | ||
| settings=data_for_test.get('extra')) | ||
| if date: | ||
| data_for_test['date'] = date | ||
| data_for_test['error'] = None | ||
| except Exception as error: | ||
| data_for_test['error'] = str(error) | ||
| data_for_test['date'] = None | ||
| finally: | ||
| return data_for_test |
Uh oh!
There was an error while loading. Please reload this page.