Skip to content

refactor: split stable_api into mixins, modularize CLI, event-driven waits - #89

Merged
cleitonleonel merged 40 commits into
cleitonleonel:masterfrom
victalejo:master
May 13, 2026
Merged

refactor: split stable_api into mixins, modularize CLI, event-driven waits#89
cleitonleonel merged 40 commits into
cleitonleonel:masterfrom
victalejo:master

Conversation

@victalejo

Copy link
Copy Markdown
Contributor

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 replaces asyncio.sleep polling with event-driven waits.

  • pyquotex/stable_api.py: 1640 → 169 líneas (-90%)
  • app.py: 1437 → 8 líneas (-99%, ahora shim a pyquotex.cli)
  • 0 polling loops en stable_api.py donde existe productor en el WS handler
  • +29 tests de regresión (snapshot de surface, firmas, imports legacy, CLI smoke, wait primitives)
  • Versión: 1.0.3 → 1.1.0

¿Qué cambia?

Nuevos paquetes privados

  • pyquotex/_api/ — 5 mixins por dominio (account, trading, history, realtime, assets) composados en Quotex vía MRO
  • pyquotex/_api/_waits.pyWaitableSlot[T], SlotRegistry, wait_until, backoff_sleep
  • pyquotex/exceptions.pyQuotexTimeoutError (hereda de TimeoutError)

CLI modularizado

  • pyquotex/cli/ con parser.py, runtime.py, formatters.py, __main__.py
  • pyquotex/cli/commands/ con 7 módulos por dominio y 27 handlers
  • argparse mantenido (cero dependencias nuevas)
  • app.py reducido a un shim de 8 líneas

Polling → eventos

Migrados a WaitableSlot.wait():

  • get_balance (slot balance)
  • buy / open_pending (slots buy_confirm, pending_confirm)
  • check_win (slot keyed win_result(operation_id))
  • get_candle_v2 (slot keyed candle_v2(asset))
  • check_connect (via wait_until)
  • Login / reconnect retries via backoff_sleep (exponencial + jitter)

Garantías de retrocompat

  • from pyquotex.stable_api import Quotex y todos sus métodos siguen idénticos
  • Test tests/test_api_surface.py compara contra snapshot JSON; cualquier drop de método o cambio de firma falla CI
  • python app.py <cmd> sigue funcionando (shim) — además ahora python -m pyquotex <cmd> también

Bugs 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 TODO en código para investigación futura del tráfico WS:

  • edit_practice_balancetraining_balance_edit_request
  • sell_optionsold_options_respond
  • get_history_linehistorical_candles

Bug adicional arreglado de paso: literal % sin escapar en argparse help 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 passed
  • poetry run python app.py --help → usage text sin traceback
  • poetry run python -m pyquotex --help → mismo usage text
  • python -c "from pyquotex.stable_api import Quotex" → ok
  • Smoke manual con credenciales reales: python app.py login, python app.py balance, python app.py candles --asset EURUSD

Docs

  • Spec: docs/superpowers/specs/2026-05-11-architecture-maintainability-design.md
  • Plan paso a paso: docs/superpowers/plans/2026-05-11-architecture-maintainability.md

victalejo and others added 30 commits May 11, 2026 19:34
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).
victalejo and others added 10 commits May 11, 2026 22:02
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>
@cleitonleonel
cleitonleonel merged commit fce2efd into cleitonleonel:master May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants