summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/asyncio/engine.py
Commit message (Collapse)AuthorAgeFilesLines
* write out a full doc for both run_sync methodsMike Bayer2023-04-261-3/+42
| | | | | Fixes: #9705 Change-Id: I705463b9984f207f2268da6ebd80f07b68aa08f0
* run docs syncMike Bayer2023-04-171-13/+11
| | | | | | this is needed due to previous commit f7bfa04bcae1e9baf Change-Id: I7fa397707a018455a2c96b62704dfd506742b0cd
* Merge "Add pool creation functions" into mainmike bayer2023-04-131-1/+16
|\
| * Add pool creation functionsFederico Caselli2023-04-121-1/+16
| | | | | | | | | | | | | | | | | | | | Added :func:`_sa.create_pool_from_url` and :func:`_asyncio.create_async_pool_from_url` to create a :class:`_pool.Pool` instance from an input url passed as string or :class:`_sa.URL`. Fixes: #9613 Change-Id: Icd8aa3f2849e6fd1bc5341114f3ef8d216a2c543
* | Remove old versionadded and versionchangedFederico Caselli2023-04-121-2/+0
|/ | | | | | | Removed versionadded and versionchanged for version prior to 1.2 since they are no longer useful. Change-Id: I5c53d1188bc5fec3ab4be39ef761650ed8fa6d3e
* allow multiparams with scalarsMike Bayer2023-03-011-3/+3
| | | | | | | | Fixed bug where the :meth:`_engine.Connection.scalars` method was not typed as allowing a multiple-parameters list, which is now supported using insertmanyvalues operations. Change-Id: I65e22c3bee80fc226d484ff1424421dd78520fa5
* Fix a syntax error in `AsyncConnection.stream_scalars()` example. (#9334)Vytautas Liuolia2023-02-191-1/+1
|
* happy new year 2023Mike Bayer2023-01-031-1/+1
| | | | Change-Id: I625af65b3fb1815b1af17dc2ef47dd697fdc3fb1
* Support result.close() for all iterator patternsMike Bayer2022-11-031-57/+105
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This change contains new features for 2.0 only as well as some behaviors that will be backported to 1.4. For 1.4 and 2.0: Fixed issue where the underlying DBAPI cursor would not be closed when using :class:`_orm.Query` with :meth:`_orm.Query.yield_per` and direct iteration, if a user-defined exception case were raised within the iteration process, interrupting the iterator. This would lead to the usual MySQL-related issues with server side cursors out of sync. For 1.4 only: A similar scenario can occur when using :term:`2.x` executions with direct use of :class:`.Result`, in that case the end-user code has access to the :class:`.Result` itself and should call :meth:`.Result.close` directly. Version 2.0 will feature context-manager calling patterns to address this use case. However within the 1.4 scope, ensured that ``.close()`` methods are available on all :class:`.Result` implementations including :class:`.ScalarResult`, :class:`.MappingResult`. For 2.0 only: To better support the use case of iterating :class:`.Result` and :class:`.AsyncResult` objects where user-defined exceptions may interrupt the iteration, both objects as well as variants such as :class:`.ScalarResult`, :class:`.MappingResult`, :class:`.AsyncScalarResult`, :class:`.AsyncMappingResult` now support context manager usage, where the result will be closed at the end of iteration. Corrected various typing issues within the engine and async engine packages. Fixes: #8710 Change-Id: I3166328bfd3900957eb33cbf1061d0495c9df670
* Improve typings of execution optionsFederico Caselli2022-11-021-19/+59
| | | | | Fixes: #8605 Change-Id: I4aec83b9f321462427c3f4ac941c3b272255c088
* the future is hereMike Bayer2022-10-021-10/+10
| | | | | | | | | the autodoc for the "future" Engine / Connection were removed, so all these links weren't working. Replace all _future for these with _engine. There was just one _future pointing to select, changed that separately. Change-Id: Ib28270d8da8616b533953204e22eabee9388d620
* Improved usage of ``asyncio.shield()``Federico Caselli2022-09-151-2/+4
| | | | | Fixes: #8516 Change-Id: Ifd8f5e5f42d9fbcd5b8d00bddc81ff6be690a75e
* Merge "add shield() in aexit" into mainmike bayer2022-07-181-4/+8
|\
| * add shield() in aexitFederico Caselli2022-07-181-4/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Added ``asyncio.shield()`` to the connection and session release process specifically within the ``__aexit__()`` context manager exit, when using :class:`.AsyncConnection` or :class:`.AsyncSession` as a context manager that releases the object when the context manager is complete. This appears to help with task cancellation when using alternate concurrency libraries such as ``anyio``, ``uvloop`` that otherwise don't provide an async context for the connection pool to release the connection properly during task cancellation. Fixes: #8145 Change-Id: I0b1ea9c3a22a18619341cbb8591225fcd339042c
* | add contextmanager typing, open run_sync typingMike Bayer2022-07-181-7/+7
|/ | | | | | | | | | | | was missing AsyncConnection type for the async context manager. fixing that revealed that _SyncConnectionCallable and _SyncSessionCallable protocols are infeasible because the given callable can have a lot of different signatures that are compatible. Change-Id: I559aa3dd88a902d0e7681c52223bb4bc0890adc1
* establish sessionmaker and async_sessionmaker as genericMike Bayer2022-05-311-3/+8
| | | | | | | | | | | This is so that custom Session and AsyncSession classes can be typed for these factories. Added appropriate typevars to `__call__()`, `__enter__()` and other methods so that a custom Session or AsyncSession subclass is carried through. Fixes: #7656 Change-Id: Ia2b8c1f22b4410db26005c3285f6ba3d13d7f0e0
* update for flake8-future-imports 0.0.5Mike Bayer2022-05-141-2/+0
| | | | | | | | a whole bunch of errors were apparently blocked by 0.0.4 being installed. Fixes: #8020 Change-Id: I22a0faeaabe03de501897893391946d677c2df7e
* pep484 ORM / SQL result supportMike Bayer2022-04-271-8/+124
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | after some experimentation it seems mypy is more amenable to the generic types being fully integrated rather than having separate spin-off types. so key structures like Result, Row, Select become generic. For DML Insert, Update, Delete, these are spun into type-specific subclasses ReturningInsert, ReturningUpdate, ReturningDelete, which is fine since the "row-ness" of these constructs doesn't happen until returning() is called in any case. a Tuple based model is then integrated so that these objects can carry along information about their return types. Overloads at the .execute() level carry through the Tuple from the invoked object to the result. To suit the issue of AliasedClass generating attributes that are dynamic, experimented with a custom subclass AsAliased, but then just settled on having aliased() lie to the type checker and return `Type[_O]`, essentially. will need some type-related accessors for with_polymorphic() also. Additionally, identified an issue in Update when used "mysql style" against a join(), it basically doesn't work if asked to UPDATE two tables on the same column name. added an error message to the specific condition where it happens with a very non-specific error message that we hit a thing we can't do right now, suggest multi-table update as a possible cause. Change-Id: I5eff7eefe1d6166ee74160b2785c5e6a81fa8b95
* pep484: schema APIMike Bayer2022-04-151-3/+4
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | implement strict typing for schema.py this module has lots of public API, lots of old decisions and very hard to follow construction sequences in many cases, and is also where we get a lot of new feature requests, so strict typing should help keep things clean. among improvements here, fixed the pool .info getters and also figured out how to get ColumnCollection and related to be covariant so that we may set them up as returning Column or ColumnClause without any conflicts. DDL was affected, noting that superclasses of DDLElement (_DDLCompiles, added recently) can now be passed into "ddl_if" callables; reorganized ddl into ExecutableDDLElement as a new name for DDLElement and _DDLCompiles renamed to BaseDDLElement. setting up strict also located an API use case that is completely broken, which is connection.execute(some_default) returns a scalar value. This case has been deprecated and new paths have been set up so that connection.scalar() may be used. This likely wasn't possible in previous versions because scalar() would assume a CursorResult. The scalar() change also impacts Session as we have explicit support (since someone had reported it as a regression) for session.execute(Sequence()) to work. They will get the same deprecation message (which omits the word "Connection", just uses ".execute()" and ".scalar()") and they can then use Session.scalar() as well. Getting this to type correctly while still supporting ORM use cases required some refactoring, and I also set up a keyword only delimeter for Session.execute() and related as execution_options / bind_arguments should always be keyword only, applied these changes to AsyncSession as well. Additionally simpify Table __init__ now that we are Python 3 only, we can have positional plus explicit kwargs finally. Simplify Column.__init__ as well again taking advantage of kw only arguments. Fill in most/all __init__ methods in sqltypes.py as the constructor for types is most of the API. should likely do this for dialect-specific types as well. Apply _InfoType for all info attributes as should have been done originally and update descriptor decorators. Change-Id: I3f9f8ff3f1c8858471ff4545ac83d68c88107527
* pep-484: asyncioMike Bayer2022-04-111-170/+231
| | | | | | | | | | | | | | | | | | | | | in this patch the asyncio/events.py module, which existed only to raise errors when trying to attach event listeners, is removed, as we were already coding an asyncio-specific workaround in upstream Pool / Session to raise this error, just moved the error out to the target and did the same thing for Engine. We also add an async_sessionmaker class. The initial rationale here is because sessionmaker() is hardcoded to Session subclasses, and there's not a way to get the use case of sessionmaker(class_=AsyncSession) to type correctly without changing the sessionmaker() symbol itself to be a function and not a class, which gets too complicated for what this is. Additionally, _SessionClassMethods has only three methods on it, one of which is not usable with asyncio (close_all()), the others not generally used from the session class. Change-Id: I064a5fa5d91cc8d5bbe9597437536e37b4e801fe
* use code generation for scoped_sessionMike Bayer2022-04-121-2/+273
| | | | | | | | | | | | | | | | | | | our decorator thing generates code in any case, so point it at the file itself to generate real code for the blocks rather than doing things dynamically. this will allow typing tools to have no problem whatsoever and we also reduce import time overhead. file size will be a lot bigger though, shrugs. syntax / dupe method / etc. checking will be accomplished by our existing linting / typing / formatting tools. As we are also using "from __future__ import annotations", we also no longer have to apply quotes to generated annotations. Change-Id: I20962cb65bda63ff0fb67357ab346e9b1ef4f108
* ensure exception raised for all stream w/ sync resultMike Bayer2022-02-041-16/+3
| | | | | | | | | | | | | | | | Fixed issue where the :meth:`_asyncio.AsyncSession.execute` method failed to raise an informative exception if the ``stream_results`` execution option were used, which is incompatible with a sync-style :class:`_result.Result` object. An exception is now raised in this scenario in the same way one is already raised when using ``stream_results`` in conjunction with the :meth:`_asyncio.AsyncConnection.execute` method. Additionally, for improved stability with state-sensitive dialects such as asyncmy, the cursor is now closed when this error condition is raised; previously with the asyncmy dialect, the connection would go into an invalid state with unconsumed server side results remaining. Fixes: #7667 Change-Id: I6eb7affe08584889b57423a90258295f8b7085dc
* happy new year 2022Mike Bayer2022-01-061-1/+1
| | | | Change-Id: I49abf2607e0eb0623650efdf0091b1fb3db737ea
* Add async_engine_from_config()Nils Philippsen2021-12-101-0/+23
| | | | | | | | | | | | | Added :func:`_asyncio.async_engine_config` function to create an async engine from a configuration dict. This otherwise behaves the same as :func:`_sa.engine_from_config`. Fixes: #7301 Closes: #7302 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7302 Pull-request-sha: c7c758833b6c37b7509b8c5bed4f26ac0ccc0395 Change-Id: I64feadf95b5015c24fe0fa0dbae6755b72d1713e
* Added support for ``psycopg`` dialect.Federico Caselli2021-11-261-0/+1
| | | | | | | Both sync and async versions are supported. Fixes: #6842 Change-Id: I57751c5028acebfc6f9c43572562405453a2f2a4
* fully implement future engine and remove legacyMike Bayer2021-11-071-2/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | The major action here is to lift and move future.Connection and future.Engine fully into sqlalchemy.engine.base. This removes lots of engine concepts, including: * autocommit * Connection running without a transaction, autobegin is now present in all cases * most "autorollback" is obsolete * Core-level subtransactions (i.e. MarkerTransaction) * "branched" connections, copies of connections * execution_options() returns self, not a new connection * old argument formats, distill_params(), simplifies calling scheme between engine methods * before/after_execute() events (oriented towards compiled constructs) don't emit for exec_driver_sql(). before/after_cursor_execute() is still included for this * old helper methods superseded by context managers, connection.transaction(), engine.transaction() engine.run_callable() * ancient engine-level reflection methods has_table(), table_names() * sqlalchemy.testing.engines.proxying_engine References: #7257 Change-Id: Ib20ed816642d873b84221378a9ec34480e01e82c
* Add documentation on how to use the events with asyncioFederico Caselli2021-10-081-0/+34
| | | | | | Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com> Fixes: #6899 Change-Id: I965af321fb36d9645fe3fc2675ad9943f24e32f2
* Surface driver connection object when using a proxied dialectFederico Caselli2021-09-171-4/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | Improve the interface used by adapted drivers, like the asyncio ones, to access the actual connection object returned by the driver. The :class:`_engine._ConnectionRecord` and :class:`_engine._ConnectionFairy` now have two new attributes: * ``dbapi_connection`` always represents a DBAPI compatible object. For pep-249 drivers, this is the DBAPI connection as it always has been, previously accessed under the ``.connection`` attribute. For asyncio drivers that SQLAlchemy adapts into a pep-249 interface, the returned object will normally be a SQLAlchemy adaption object called :class:`_engine.AdaptedConnection`. * ``driver_connection`` always represents the actual connection object maintained by the third party pep-249 DBAPI or async driver in use. For standard pep-249 DBAPIs, this will always be the same object as that of the ``dbapi_connection``. For an asyncio driver, it will be the underlying asyncio-only connection object. The ``.connection`` attribute remains available and is now a legacy alias of ``.dbapi_connection``. Fixes: #6832 Change-Id: Ib72f97deefca96dce4e61e7c38ba430068d6a82e
* Add scalars method to connection and session classesMiguel Grinberg2021-09-141-0/+41
| | | | | | | | | | | | | | | | | | | | Added new methods :meth:`_orm.Session.scalars`, :meth:`_engine.Connection.scalars`, :meth:`_asyncio.AsyncSession.scalars` and :meth:`_asyncio.AsyncSession.stream_scalars`, which provide a short cut to the use case of receiving a row-oriented :class:`_result.Result` object and converting it to a :class:`_result.ScalarResult` object via the :meth:`_engine.Result.scalars` method, to return a list of values rather than a list of rows. The new methods are analogous to the long existing :meth:`_orm.Session.scalar` and :meth:`_engine.Connection.scalar` methods used to return a single value from the first row only. Pull request courtesy Miguel Grinberg. Fixes: #6990 Closes: #6991 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/6991 Pull-request-sha: b3e0bb3042c55b0cc5af6a25cb3f31b929f88a47 Change-Id: Ia445775e24ca964b0162c2c8e5ca67dd1e39199f
* Improve error message when inspecting async proxiesFederico Caselli2021-08-301-0/+23
| | | | | | | Provide better error message when trying to insepct and async engine or asnyc connection. Change-Id: I907f3a22c6b76fe43df9d40cb0e69c57f74a7982
* Replace all http:// links to https://Federico Caselli2021-07-041-1/+1
| | | | | | Also replace http://pypi.python.org/pypi with https://pypi.org/project Change-Id: I84b5005c39969a82140706472989f2a30b0c7685
* Implement proxy back reference system for asyncioMike Bayer2021-06-021-18/+38
| | | | | | | | | | | | | | | | | | | | | | | | | | Implemented a new registry architecture that allows the ``Async`` version of an object, like ``AsyncSession``, ``AsyncConnection``, etc., to be locatable given the proxied "sync" object, i.e. ``Session``, ``Connection``. Previously, to the degree such lookup functions were used, an ``Async`` object would be re-created each time, which was less than ideal as the identity and state of the "async" object would not be preserved across calls. From there, new helper functions :func:`_asyncio.async_object_session`, :func:`_asyncio.async_session` as well as a new :class:`_orm.InstanceState` attribute :attr:`_orm.InstanceState.asyncio_session` have been added, which are used to retrieve the original :class:`_asyncio.AsyncSession` associated with an ORM mapped object, a :class:`_orm.Session` associated with an :class:`_asyncio.AsyncSession`, and an :class:`_asyncio.AsyncSession` associated with an :class:`_orm.InstanceState`, respectively. This patch also implements new methods :meth:`_asyncio.AsyncSession.in_nested_transaction`, :meth:`_asyncio.AsyncSession.get_transaction`, :meth:`_asyncio.AsyncSession.get_nested_transaction`. Fixes: #6319 Change-Id: Ia452a7e7ce9bad3ff8846c7dea8d45c839ac9fac
* Remove pep484 type comments from the codeFederico Caselli2021-05-161-44/+28
| | | | | | | | | | | | | Current effort is around the stub package, and having typing in two places makes thing worse, since the types here are usually outdated compared to the version in the stubs. Once v2 gets under way we can start consolidating the types here. Fixes: #6461 Change-Id: I7132a444bd7138123074bf5bc664b4bb119a85ce
* unify transactional context managersMike Bayer2021-05-051-17/+10
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Applied consistent behavior to the use case of calling ``.commit()`` or ``.rollback()`` inside of an existing ``.begin()`` context manager, with the addition of potentially emitting SQL within the block subsequent to the commit or rollback. This change continues upon the change first added in :ticket:`6155` where the use case of calling "rollback" inside of a ``.begin()`` contextmanager block was proposed: * calling ``.commit()`` or ``.rollback()`` will now be allowed without error or warning within all scopes, including that of legacy and future :class:`_engine.Engine`, ORM :class:`_orm.Session`, asyncio :class:`.AsyncEngine`. Previously, the :class:`_orm.Session` disallowed this. * The remaining scope of the context manager is then closed; when the block ends, a check is emitted to see if the transaction was already ended, and if so the block returns without action. * It will now raise **an error** if subsequent SQL of any kind is emitted within the block, **after** ``.commit()`` or ``.rollback()`` is called. The block should be closed as the state of the executable object would otherwise be undefined in this state. Fixes: #6288 Change-Id: I8b21766ae430f0fa1ac5ef689f4c0fb19fc84336
* add missing copyright to asyncio filesMike Bayer2021-03-021-0/+7
| | | | Change-Id: I6028b70bba76d6d9e1042762423d61f83644c085
* Detect non async driver on engine creationFederico Caselli2021-02-061-0/+5
| | | | | | | | | An error is raised when creating an async engine with an incompatible dbapi. Before the error was raised only when first using the engine. Change-Id: I977952b4c03ae51f568749ad744c545197bcd887 Reference: #5920
* Fix a couple of bugs in the asyncio implementationFederico Caselli2021-01-211-1/+0
| | | | | | | | | | | | | Log an informative message if a connection is not closed and the gc is reclaiming it when using an async dpapi, that does not support running IO at that stage. The ``AsyncAdaptedQueue`` used by default on async dpapis should instantiate a queue only when it's first used to avoid binding it to a possibly wrong event loop. Fixes: #5823 Change-Id: Ibfc50e209b1937ae3d6599ae7997f028c7a92c33
* Improve ``run_sync`` documentation in asyncio extFederico Caselli2021-01-071-0/+10
| | | | | Change-Id: I202458ab6d81e29053118c9fb9c205b865c8d2ba Ref: #5817
* Implement connection binding for AsyncSessionMike Bayer2021-01-071-4/+106
| | | | | | | | | | | | | | | | | | | | | | | | | | | Implemented "connection-binding" for :class:`.AsyncSession`, the ability to pass an :class:`.AsyncConnection` to create an :class:`.AsyncSession`. Previously, this use case was not implemented and would use the associated engine when the connection were passed. This fixes the issue where the "join a session to an external transaction" use case would not work correctly for the :class:`.AsyncSession`. Additionally, added methods :meth:`.AsyncConnection.in_transaction`, :meth:`.AsyncConnection.in_nested_transaction`, :meth:`.AsyncConnection.get_transaction`. The :class:`.AsyncEngine`, :class:`.AsyncConnection` and :class:`.AsyncTransaction` objects may be compared using Python ``==`` or ``!=``, which will compare the two given objects based on the "sync" object they are proxying towards. This is useful as there are cases particularly for :class:`.AsyncTransaction` where multiple instances of :class:`.AsyncTransaction` can be proxying towards the same sync :class:`_engine.Transaction`, and are actually equivalent. The :meth:`.AsyncConnection.get_transaction` method will currently return a new proxying :class:`.AsyncTransaction` each time as the :class:`.AsyncTransaction` is not otherwise statefully associated with its originating :class:`.AsyncConnection`. Fixes: #5811 Change-Id: I5a3a6b2f088541eee7b0e0f393510e61bc9f986b
* Repair async test refactorMike Bayer2021-01-021-1/+1
| | | | | | | | | | | | | | | in I4940d184a4dc790782fcddfb9873af3cca844398 we reworked how async tests run but apparently the async tests in test/ext/asyncio are reporting success without being run. This patch pushes pytestplugin further so that it won't instrument any test or function overall that declares itself async. This removes the need for the __async_wrap__ flag and also allows us to use a more strict "run_async_test" function that always runs the asyncio event loop from the top. Also start working asyncio into main testing suite. Change-Id: If7144e951a9db67eb7ea73b377f81c4440d39819
* Detect non compatible execution in async modeFederico Caselli2020-12-081-0/+3
| | | | | | | | | | The SQLAlchemy async mode now detects and raises an informative error when an non asyncio compatible :term:`DBAPI` is used. Using a standard ``DBAPI`` with async SQLAlchemy will cause it to block like any sync call, interrupting the executing asyncio loop. Change-Id: I9aed87dc1b0df53e8cb2109495237038aa2cb2d4
* generalize scoped_session proxying and apply to asyncio elementsMike Bayer2020-10-101-13/+140
| | | | | | | | | | | | | | | | | | | | Reworked the proxy creation used by scoped_session() to be based on fully copied code with augmented docstrings and moved it into langhelpers. asyncio session, engine, connection can now take advantage of it so that all non-async methods are availble. Overall implementation of most important accessors / methods on AsyncConnection, etc. , including awaitable versions of invalidate, execution_options, etc. In order to support an event dispatcher on the async classes while still allowing them to hold __slots__, make some adjustments to the event system to allow that to be present, at least rudimentally. Fixes: #5628 Change-Id: I5eb6929fc1e4fdac99e4b767dcfd49672d56e2b2
* upgrade to black 20.8b1Mike Bayer2020-09-281-12/+12
| | | | | | | It's better, the majority of these changes look more readable to me. also found some docstrings that had formatting / quoting issues. Change-Id: I582a45fde3a5648b2f36bab96bad56881321899b
* Fix a mis-reference in create_async_engine().Fantix King2020-08-311-1/+1
| | | | | | | | | | | | | | `AsyncMethodRequired` is actually from `sqlalchemy.ext.asyncio.exc`, so here it should be referenced as `async_exc.AsyncMethodRequired`, instead of `exc.AsyncMethodRequired`. Fixes: #5529 Closes: #5545 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5545 Pull-request-sha: d8f885c587dd058f909d4f3bdbec3d0fca176680 Change-Id: I6886558bfd33d3e9e283fbd60c0ec971a1f22c0c
* Implement rudimentary asyncio support w/ asyncpgMike Bayer2020-08-131-0/+461
Using the approach introduced at https://gist.github.com/zzzeek/6287e28054d3baddc07fa21a7227904e We can now create asyncio endpoints that are then handled in "implicit IO" form within the majority of the Core internals. Then coroutines are re-exposed at the point at which we call into asyncpg methods. Patch includes: * asyncpg dialect * asyncio package * engine, result, ORM session classes * new test fixtures, tests * some work with pep-484 and a short plugin for the pyannotate package, which seems to have so-so results Change-Id: Idbcc0eff72c4cad572914acdd6f40ddb1aef1a7d Fixes: #3414