Spaces:
Running
Running
Download config.py from chesstb/explorer: direct link, hf CLI and curl.
- Browser
- Download file 17.9 kB
-
https://huggingface.co/spaces/chesstb/explorer/resolve/main/config.py
- Command line
-
hf download hf://spaces/chesstb/explorer/config.py
-
curl -L -o config.py https://huggingface.co/spaces/chesstb/explorer/resolve/main/config.py
17.9 kB
| """ | |
| config.py: ChessTB Explorer configuration | |
| Edit the values below directly, then run `python app.py`. This file | |
| contains the primary application configuration. A subset of settings is | |
| also readable from the process environment, with the literal value below | |
| used when the environment variable is unset, allowing one committed | |
| configuration file to serve multiple deployment platforms. | |
| - ADMIN_TOKEN / FLASK_SECRET_KEY: always from the environment only | |
| (no literal fallback below) so they can be set as platform secrets | |
| (e.g. a Render "Environment Variable" marked secret, or a Hugging | |
| Face Space "Secret") without ever committing them - see the "Admin" | |
| section below. | |
| - PORT: read from the environment with a literal fallback, because | |
| platforms like Render assign the port dynamically per-deployment | |
| (via the platform's PORT variable) rather than using a fixed value; | |
| see the "Server" section below. | |
| - TABLEBASE_PATH / SOURCE_REPOSITORY_URL: read from the environment with a literal fallback, | |
| to allow a deployment (for example, a Render Web Service) to select a | |
| different tablebase location or repository URL without modifying the | |
| source file; editing the literal value below remains supported. | |
| Invalid values (wrong type, out of range) make the app log an error and | |
| exit at startup rather than run with a silently-wrong configuration; | |
| see AppConfig.from_config() in app.py. | |
| """ | |
| import os | |
| # ── Tablebase location ────────────────────────────────────────────────────── | |
| # Bucket URL or local tablebase root. Full tables are preferred over shipping | |
| # tables; castling directories are selected automatically for each material. | |
| # A list of explicit source directories is also supported. | |
| TABLEBASE_PATH = os.environ.get( | |
| "TABLEBASE_PATH", "https://huggingface.co/buckets/chesstb/chesstb/resolve", | |
| ) | |
| # ── Server ─────────────────────────────────────────────────────────────────── | |
| # Set to True to run the app via Flask's own dev server (auto-reload, | |
| # detailed error pages, interactive debugger) instead of waitress. Leave | |
| # False for any deployment reachable from other machines - False is what | |
| # makes `python app.py` serve via waitress, the production WSGI server | |
| # this app ships with. | |
| DEBUG = False | |
| # Interface the server (waitress, or the Flask dev server if DEBUG is | |
| # True) binds to. Set to "0.0.0.0" so the server accepts connections | |
| # arriving at the container's network interface, as required by Render, | |
| # Hugging Face Spaces, and any other containerized deployment. Use | |
| # "127.0.0.1" instead only for single-machine local use with no other | |
| # access control in front of it - see README.md's "Security notes". | |
| HOST = "0.0.0.0" | |
| # Port the server listens on - applies whether app.py ends up serving via | |
| # waitress (DEBUG=False) or the Flask dev server (DEBUG=True). | |
| # | |
| # Read from a PORT environment variable first, falling back to 7860 | |
| # (Hugging Face Docker Spaces' default routed port) when unset. This | |
| # matters specifically for Render: a Render web service is assigned its | |
| # externally-routed port at deploy time via its own PORT variable | |
| # (commonly 10000, but not guaranteed) rather than a fixed value. The | |
| # container reads and binds to the injected port. Running locally with no | |
| # PORT set uses 7860. | |
| PORT = os.environ.get("PORT", "7860") | |
| # Number of worker threads waitress uses to handle concurrent requests | |
| # (only relevant when DEBUG=False). Needs to be more than 1 so that a | |
| # long-lived /probe/stream SSE connection can't block every other request; | |
| # see README.md's "Running in production" section. This is separate | |
| # from PROBE_THREADS below, which sizes the pool used internally to | |
| # parallelise tablebase probing, not to serve HTTP requests. | |
| WAITRESS_THREADS = 4 | |
| # ── Probing ────────────────────────────────────────────────────────────────── | |
| # Worker threads in the probe thread pool for evaluating a position's | |
| # legal moves in parallel. Set to match a 2-vCPU host (e.g. Hugging | |
| # Face's cpu-basic tier); raise it on a larger host, or leave as None to | |
| # scale automatically instead (min(16, cpu_count() * 2)). | |
| PROBE_THREADS = 2 | |
| # Minimum number of not-yet-cached child positions before a probe batch | |
| # switches from sequential to the PROBE_THREADS thread pool. Lower this | |
| # (or set to 1 to always parallelize) for workloads with many legal moves | |
| # or when profiling shows sequential probing is a bottleneck. Raise it | |
| # when thread hand-off overhead outweighs the benefit for the workload. | |
| PROBE_PARALLEL_THRESHOLD = 4 | |
| # Wall-clock timeout, in seconds, for each probe phase. /probe gives the | |
| # root probe and the child-move batch independent budgets, so a worst-case | |
| # request can consume up to two PROBE_TIMEOUT_SECS intervals. /probe/stream | |
| # uses the same setting for its root probe and child pre-warm/evaluation | |
| # phases. Remaining probes are treated as unknown when a phase reaches its | |
| # deadline rather than blocking the request indefinitely. | |
| PROBE_TIMEOUT_SECS = 30 | |
| # ── Caching ────────────────────────────────────────────────────────────────── | |
| # Defaults target one process on Hugging Face CPU Basic (2 vCPU, 16 GB | |
| # RAM). With the four default remote sources, byte-cache budgets total | |
| # 8.5 GiB: four 2 GiB page caches plus one shared 512 MiB block cache. | |
| # These fill on demand. Leave the remaining RAM for result caches, | |
| # table/index metadata, Python object overhead, and active requests. | |
| # Max entries in the root-FEN result cache (full JSON responses served by | |
| # evaluate_fen()). Raising this keeps more distinct root positions warm | |
| # across a session at the cost of memory. | |
| EVALUATE_CACHE_SIZE = 16384 | |
| # Max entries in the child-position probe cache (raw WDL/DTZ/DTC/DTM/DTM50 | |
| # tuples served by _probe_fen()), shared between root probes and every | |
| # child position probed while ranking moves. | |
| PROBE_CACHE_SIZE = 262144 | |
| # Size (in bytes) of chess.chesstb's own internal cache of decoded/ | |
| # decompressed tablebase blocks, shared across the WDL/DTZ/DTC/DTM/DTM50 | |
| # tables. | |
| # One cache is shared across every source. Keep 512 MiB of decoded | |
| # blocks to reduce repeated decompression on the 2-vCPU host. | |
| BLOCK_CACHE_BYTES = 512 * 1024 * 1024 | |
| # ── Remote (URL) tablebase entries only ───────────────────────────────────── | |
| # Everything below is ignored for a local-directory entry; it applies to | |
| # every http(s):// entry in TABLEBASE_PATH (including entries inside | |
| # a hybrid list - see | |
| # above). See the module docstrings of remote/remote_direct.py and | |
| # remote/remote_fallback.py for the full design. | |
| # How a remote table's bytes reach the prober: | |
| # | |
| # "direct" -- probe the remote tables in place, fetching only the | |
| # page-sized byte ranges each probe reads, nothing written | |
| # to disk (remote/remote_direct.py). | |
| # "download" -- fetch each table file in full on first touch and cache it | |
| # on local disk (remote/remote_fallback.py). Slower first | |
| # touch of a material and real disk usage, but every read | |
| # after it is a local mmap read. | |
| # | |
| # "direct" is the preferred mode for browsing across many materials. | |
| # "download" is preferable when a small set of materials is accessed | |
| # repeatedly for a long session or when per-request network latency is high. | |
| # | |
| # "direct" is also the right choice for a PaaS deployment (e.g. Render) | |
| # fronting a multi-terabyte tablebase set: it needs no persistent disk at | |
| # all, so it sidesteps both the platform's per-GB disk cost at that scale | |
| # and the fact that most platforms' single-service disks aren't sized for | |
| # terabytes in the first place. "download" would need a disk (or enough | |
| # ephemeral local storage) large enough for the materials probed during a | |
| # session. Storage requirements therefore grow with the number and size of | |
| # materials accessed. | |
| REMOTE_MODE = "direct" | |
| # Soft budget, in bytes, PER remote source in TABLEBASE_PATH, shared | |
| # across that source's tables: bounds the in-memory page cache in | |
| # "direct" mode, and the on-disk whole-file cache in "download" mode. | |
| # Four default sources can retain 8 GiB of pages in total. Revisit this | |
| # budget when adding sources or running multiple application processes. | |
| REMOTE_PAGE_CACHE_BYTES = 2 * 1024 * 1024 * 1024 | |
| # Size, in bytes, of one page. In "direct" mode this is the granularity of | |
| # every fetch, so it sets the trade between over-fetching (too large) and | |
| # many round trips per probe (too small) -- 256 KiB keeps a table's whole | |
| # header + index region within a page or two, which is what makes later | |
| # probes of the same table cheap. In "download" mode it is only the chunk | |
| # size used while streaming a full file down, and has no effect on which | |
| # bytes end up fetched. | |
| REMOTE_PAGE_SIZE_BYTES = 256 * 1024 | |
| # Per-HTTP-request timeout, in seconds, for both existence/size checks and | |
| # the download itself against a remote tablebase. | |
| REMOTE_TIMEOUT_SECS = 20.0 | |
| # Number of attempts for a single remote request before giving up (the | |
| # request that triggered it then fails, surfacing as a probe error). | |
| REMOTE_MAX_RETRIES = 3 | |
| # Max size of the HTTP connection pool each remote backend's session | |
| # keeps open against its entry's host. Left as None so it scales | |
| # with PROBE_THREADS automatically (max(PROBE_THREADS * 2, 20)) - set an | |
| # explicit integer to override. Too small a pool under-serves concurrent | |
| # probing (connections stop being reused once PROBE_THREADS exceeds it); | |
| # too large rarely hurts beyond a handful of idle sockets, so there's | |
| # little reason to set this below the auto-computed value. | |
| REMOTE_POOL_MAXSIZE = None | |
| # ── Rate limiting ──────────────────────────────────────────────────────────── | |
| # Per-client-IP request limit applied to /probe and /probe/stream - the | |
| # two public, unauthenticated, CPU-bound endpoints - using flask-limiter's | |
| # string syntax (e.g. "60 per minute", "10 per second"; multiple limits | |
| # can be combined with ";"). Backed by an in-process memory store, which | |
| # is enough for a single-container deployment (e.g. one Render Web | |
| # Service instance) but does not share state across multiple replicas. | |
| # Left empty, rate limiting is disabled entirely. | |
| # | |
| # 60 per minute comfortably covers normal browsing and autoplay at its | |
| # default 1.25s delay; raise it if legitimate autoplay at a fast delay | |
| # setting gets throttled, or lower it if the deployment sees abuse. | |
| PROBE_RATE_LIMIT = "60 per minute" | |
| # Per-client-IP limit applied only to requests that identify themselves as | |
| # background hover-prefetch via the "X-Prefetch: 1" request header (see | |
| # frontend/src/engine/tablebase.js's hover-prefetch). Deliberately separate from, | |
| # and tighter than, PROBE_RATE_LIMIT -- a client-supplied header is not | |
| # trusted enough to exempt a request from limiting altogether (any | |
| # client could tag every request "prefetch" to bypass PROBE_RATE_LIMIT), | |
| # only to route it to its own, smaller budget, so a sustained hover sweep | |
| # can burn through this budget and start getting silently-dropped 429s on | |
| # prefetches without ever touching the user's own click budget above. | |
| # Left empty, prefetch-tagged requests fall back to sharing | |
| # PROBE_RATE_LIMIT with everything else, as if this setting didn't exist. | |
| PREFETCH_RATE_LIMIT = "30 per minute" | |
| # Per-client-IP limit on /admin/login (config.py's ADMIN_TOKEN check), | |
| # same string syntax as PROBE_RATE_LIMIT above and backed by the same | |
| # in-process memory store. Separate from PROBE_RATE_LIMIT because | |
| # /admin/login is a brute-forceable shared-secret check, not a CPU-bound | |
| # probe -- it needs a tighter limit than "comfortably covers normal | |
| # browsing" calls for. Applies to both failed and successful login | |
| # attempts. Left empty, login attempts are not rate limited at all. | |
| ADMIN_LOGIN_RATE_LIMIT = "5 per minute" | |
| # Number of trusted reverse-proxy hops in front of this application. | |
| # PROBE_RATE_LIMIT and ADMIN_LOGIN_RATE_LIMIT above both key on the | |
| # client's IP address (request.remote_addr) -- behind a reverse proxy | |
| # that's the proxy's own address unless it's corrected for, which would | |
| # collapse every client behind that proxy onto one shared rate-limit key. | |
| # The same trusted-hop count also governs whether X-Forwarded-Proto is | |
| # trusted, which corrects request.scheme / request.is_secure (and any | |
| # externally-generated URL) when TLS terminates at that reverse proxy | |
| # rather than at this app itself. | |
| # | |
| # In production (DEBUG = False), app.py passes this to waitress's own | |
| # trusted_proxy_count, since waitress -- the actual production WSGI | |
| # server -- parses X-Forwarded-For/X-Forwarded-Proto and corrects | |
| # REMOTE_ADDR/wsgi.url_scheme itself, before Flask ever sees the request. | |
| # app.py also applies Werkzeug's ProxyFix with the same count, which | |
| # makes the equivalent correction for the DEBUG = True Flask-dev-server | |
| # path (waitress isn't involved there at all); under waitress it is | |
| # redundant, since waitress performs the same correction itself. | |
| # | |
| # 1 is correct for this project's own render.yaml / "Deploying to Render" | |
| # topology: Render's platform edge/load-balancer sits in front of a web | |
| # service exactly once. Increase it when an additional reverse proxy is | |
| # present in front of the service. Set to 0 when no reverse proxy is present | |
| # -- a value that does not match the actual proxy chain | |
| # leaves the rate limiter keying on the wrong address (or the scheme | |
| # misdetected), or (set too high for no proxy at all) trusting a | |
| # client-supplied X-Forwarded-For/X-Forwarded-Proto header directly, | |
| # letting a client spoof its own rate-limit key or apparent scheme. | |
| TRUSTED_PROXY_COUNT = os.environ.get("TRUSTED_PROXY_COUNT", "1") | |
| # ── Admin ──────────────────────────────────────────────────────────────────── | |
| # Shared secret required to reach /admin, /admin/cache/stats, and | |
| # /admin/cache/clear. Read from the ADMIN_TOKEN environment variable | |
| # rather than hardcoded here - set it as a platform secret (e.g. a | |
| # Render "Environment Variable" marked secret, generated automatically by | |
| # render.yaml's generateValue: true) rather than committing it here. | |
| # Left unset, every admin route responds 503 rather than running without | |
| # a password. Visiting /admin with no active session shows a login form; | |
| # a correct token there starts a signed session cookie. API clients may | |
| # instead send "Authorization: Bearer <ADMIN_TOKEN>" directly. | |
| ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN", "") | |
| # Key Flask uses to sign the admin session cookie. Read from the | |
| # FLASK_SECRET_KEY environment variable for the same reason as | |
| # ADMIN_TOKEN above. Left unset, a random key is generated at process | |
| # startup instead - sessions still work, but every restart invalidates | |
| # existing ones, requiring the admin to log in again. Set an explicit, | |
| # persistent value if that's undesirable (e.g. behind a load balancer | |
| # with multiple replicas, or to survive routine restarts). | |
| FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", "") | |
| # Whether the admin session cookie set by /admin/login requires HTTPS. | |
| # Independent of DEBUG above -- DEBUG is a development/runtime behavior | |
| # switch, not a reliable indicator of the deployment's transport security, | |
| # so a deployment can legitimately run DEBUG=False (production-like | |
| # behavior) while still being served over plain HTTP. Leaving this True | |
| # for such a deployment sets the cookie Secure, which a browser then | |
| # won't send back over a plain HTTP connection -- /admin/login would | |
| # appear to succeed while every subsequent authenticated /admin/* request | |
| # came back 401. | |
| # | |
| # Read from the environment, like TRUSTED_PROXY_COUNT above, so a | |
| # deployment (e.g. render.yaml's Render blueprint, which sets this to | |
| # "true" explicitly) can configure it without editing this file. Defaults | |
| # to False (via SESSION_COOKIE_SECURE unset or anything other than | |
| # "true") because that's what a local installation needs (README.md's | |
| # "Installation"): `python app.py` served over http://127.0.0.1:7860 -- | |
| # plain HTTP -- after editing only TABLEBASE_PATH above, with DEBUG and | |
| # this setting both left at their defaults. | |
| # | |
| # Set this to "true" for any deployment the browser reaches over HTTPS, | |
| # including one behind a TLS-terminating reverse proxy such as Render's | |
| # (the browser's own connection is what the Secure attribute governs, not | |
| # the internal hop between the proxy and this process). | |
| SESSION_COOKIE_SECURE = os.environ.get("SESSION_COOKIE_SECURE", "false").strip().lower() == "true" | |
| # ── Links ──────────────────────────────────────────────────────────────────── | |
| # Repository URL for the source repository button shown in the header of every page. | |
| # Left empty, the button is omitted entirely. Also readable from a | |
| # SOURCE_REPOSITORY_URL environment variable (falling back to the literal string | |
| # below when unset), so a platform deployment can set it from its own | |
| # dashboard without a commit - see render.yaml. | |
| SOURCE_REPOSITORY_URL = os.environ.get( | |
| "SOURCE_REPOSITORY_URL", | |
| "https://huggingface.co/spaces/chesstb/explorer/tree/main", | |
| ) | |