refactor: split stable_api into mixins, modularize CLI, event-driven waits - #89
Merged
Conversation
Captures the brainstormed design for splitting stable_api.py into domain mixins, modularizing the argparse CLI, and replacing asyncio.sleep polling loops with event-driven waits. 100% backwards compatible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step-by-step plan covering: safety-net tests (Phase 0), wait helpers (Phase 1), polling-to-event migration (Phase 2), mixin extraction (Phase 3), CLI modularization (Phase 4), cleanup and version bump (Phase 5). ~19 tasks across 16-20 commits on refactor/architecture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
argparse renders subparser help via printf-style %-formatting against action.__dict__. The literal "%" in "Show payout % for ..." was being interpreted as a format specifier and crashing with TypeError. Doubling to %% renders correctly. Pre-existing bug surfaced by Phase 0 CLI smoke test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The training_balance_edit_request attribute has no producer in any WS handler, so Task 2.4 cannot migrate this polling loop to a slot until the correct WS event is identified. Leaving the existing (broken) polling in place so behavior is unchanged.
Adds buy_confirm slot to SlotRegistry; fires buy_confirm and pending_confirm from WS handler in pyquotex/api.py wherever a confirmation id is received. Consumers (buy, open_pending) now await the slot instead of polling. sell_option deferred (no producer for sold_options_respond).
Commit b048669 inadvertently changed buy() and open_pending() to raise QuotexTimeoutError on timeout instead of returning (False, 'Timeout'). This broke CLI callers (app.py:866, 973) that unpack the return as a tuple. Restore the original return shape while keeping the slot-based wait. Also make QuotexTimeoutError inherit from TimeoutError so callers catching TimeoutError continue to work.
Adds per-asset candle_v2 slot to SlotRegistry; fires it from the WS candle-arrival handler. get_candle_v2 now awaits the slot instead of polling. get_history_line deferred (no producer for historical_candles).
The two login.py sleeps are linear settle-delays (not counted retry loops) so backoff_sleep(0) preserves the ~1s pacing today, with a TODO to introduce a proper attempt counter when a retry loop is added. The api.py:141 sleep is the heartbeat tick interval (pacing for a periodic keepalive), not a retry-after-error sleep, so it correctly stays as asyncio.sleep(5) with a TODO explaining why backoff_sleep would be the wrong primitive here.
Moves 11 account-related methods (connect, reconnect, get_balance, get_profile, get_server_time, change_account, change_time_offset, set_account_mode, edit_practice_balance, store_settings_apply, start_remaing_time) from the monolithic stable_api.py into a focused mixin file. Quotex now inherits from AccountMixin; public surface unchanged (verified by tests/test_api_surface.py).
Moves 7 trade-execution methods (buy, sell_option, open_pending, check_win, get_result, get_profit, get_history) from the monolithic stable_api.py into a focused mixin. Quotex inherits from TradingMixin; public surface unchanged (verified by tests/test_api_surface.py).
Moves 9 history/candle methods (get_candles, _fetch_historical_batch, _parse_historical_candles, get_historical_candles, get_candles_deep, get_candle_v2, get_history_line, get_trader_history, prepare_candles) from stable_api.py into a focused mixin. Public surface unchanged.
Moves 16 streaming/indicator methods from stable_api.py into a focused mixin (start_*_stream, get_realtime_*, subscribe_indicator, calculate_indicator, start_mood_stream, opening_closing_current_candle, get_signal_data). Public surface unchanged.
Final mixin extraction. Moves 7 asset metadata/payout methods (get_instruments, get_all_asset_name, get_available_asset, check_asset_open, get_all_assets, get_payment, get_payout_by_asset) into the AssetsMixin. After this commit, stable_api.py is a thin facade containing only Quotex.__init__, websocket, set_session, check_connect, _check_connect, close, and re_subscribe_stream; all domain methods live in pyquotex/_api/*. Public surface unchanged.
Distributes the 27 cmd_* CLI handlers from app.py into seven per-domain modules under pyquotex/cli/commands/. The COMMAND_REGISTRY dict maps argparse subcommand names to handlers. app.py is left non-functional until Phase 4.3 wires the new pyquotex.cli.__main__ entry point and Phase 4.4 reduces app.py to a thin shim.
Builds the Quotex client, calls connect_with_retry, dispatches to the COMMAND_REGISTRY handler for args.command, and runs cleanup in finally. Replaces the inline main() coroutine that lived in app.py.
app.py becomes a 5-line shim calling pyquotex.cli.__main__.cli_main(), preserving the documented 'python app.py <command>' usage. The pyquotex/__main__.py module is also routed to the new entry point. CLI smoke test passes again.
Architecture refactor (no public API changes): stable_api split into domain mixins, CLI modularized, polling replaced with event-driven waits. Minor version bump reflects internal restructure with full backwards compatibility.
…waits Architecture & maintainability refactor on branch refactor/architecture. 37 commits across 6 phases (safety net, wait primitives, polling→events, mixin extraction, CLI modularization, cleanup). Results: - pyquotex/stable_api.py: 1640 → 169 lines (-90%) - app.py: 1437 → 8 lines (-99%, now a shim to pyquotex.cli) - Polling loops replaced by typed WaitableSlot + SlotRegistry where a WS producer exists; remaining (3 cases) documented with TODO when no producer was identified. - New private package pyquotex._api/ with 5 domain mixins (account, trading, history, realtime, assets) composed into Quotex via MRO. - New pyquotex.cli/ package replaces the monolithic app.py CLI; 27 commands organized into 7 per-domain modules. - 29-test regression safety net (API surface snapshot + signature parity + import compat + CLI smoke + wait primitives). - Version bump 1.0.3 → 1.1.0; public API surface unchanged. Spec: docs/superpowers/specs/2026-05-11-architecture-maintainability-design.md Plan: docs/superpowers/plans/2026-05-11-architecture-maintainability.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Architecture & maintainability refactor with 100% backwards-compatible public API. Splits the monolithic
stable_api.py(1640 lines) into domain mixins, modularizes the CLI, and replacesasyncio.sleeppolling with event-driven waits.pyquotex/stable_api.py: 1640 → 169 líneas (-90%)app.py: 1437 → 8 líneas (-99%, ahora shim apyquotex.cli)stable_api.pydonde existe productor en el WS handler¿Qué cambia?
Nuevos paquetes privados
pyquotex/_api/— 5 mixins por dominio (account,trading,history,realtime,assets) composados enQuotexvía MROpyquotex/_api/_waits.py—WaitableSlot[T],SlotRegistry,wait_until,backoff_sleeppyquotex/exceptions.py—QuotexTimeoutError(hereda deTimeoutError)CLI modularizado
pyquotex/cli/conparser.py,runtime.py,formatters.py,__main__.pypyquotex/cli/commands/con 7 módulos por dominio y 27 handlersargparsemantenido (cero dependencias nuevas)app.pyreducido a un shim de 8 líneasPolling → eventos
Migrados a
WaitableSlot.wait():get_balance(slotbalance)buy/open_pending(slotsbuy_confirm,pending_confirm)check_win(slot keyedwin_result(operation_id))get_candle_v2(slot keyedcandle_v2(asset))check_connect(viawait_until)backoff_sleep(exponencial + jitter)Garantías de retrocompat
from pyquotex.stable_api import Quotexy todos sus métodos siguen idénticostests/test_api_surface.pycompara contra snapshot JSON; cualquier drop de método o cambio de firma falla CIpython app.py <cmd>sigue funcionando (shim) — además ahorapython -m pyquotex <cmd>tambiénBugs pre-existentes descubiertos
3 métodos hacen polling sobre atributos que ningún WS handler asigna (sólo salen por timeout). No migrados — comportamiento original preservado, documentados con
TODOen código para investigación futura del tráfico WS:edit_practice_balance←training_balance_edit_requestsell_option←sold_options_respondget_history_line←historical_candlesBug adicional arreglado de paso: literal
%sin escapar enargparsehelp strings rompía--help.Test plan
poetry run pytest tests/test_api_surface.py tests/test_import_compat.py tests/test_cli_smoke.py tests/test_waits.py -v→ 29 passedpoetry run python app.py --help→ usage text sin tracebackpoetry run python -m pyquotex --help→ mismo usage textpython -c "from pyquotex.stable_api import Quotex"→ okpython app.py login,python app.py balance,python app.py candles --asset EURUSDDocs
docs/superpowers/specs/2026-05-11-architecture-maintainability-design.mddocs/superpowers/plans/2026-05-11-architecture-maintainability.md