Skip to content
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

feat: config hot reload via watchdog #42

Merged
merged 6 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
566 changes: 291 additions & 275 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pyaml-env = "^1.2.1"
setuptools = "^70.0.0"
parsec = { git = "https://github.com/leoslf/parsec.py", branch = "master" } # "^3.17"
retry = "^0.9.2"
watchdog = "^5.0.2"

[tool.poetry.extras]
docs = ["sphinx", "sphinx-rtd-theme", "myst-parser", "sphinx-pyproject", "sphinxcontrib-apidoc", "sphinx-autoapi"]
Expand Down
143 changes: 88 additions & 55 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" and platfo
decorator==5.1.1 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \
--hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186
more-itertools==10.3.0 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \
--hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320
more-itertools==10.5.0 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \
--hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6
parsec @ git+https://github.com/leoslf/parsec.py@48dfe687b9430150bd0febd4d860bf96f5e7e390 ; python_version >= "3.12" and python_version < "4.0"
prwlock==0.4.1 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:a12339f729f69985581e68628336446f8abf6c99aadc9fde622ed201024dd37d \
Expand All @@ -24,61 +24,94 @@ pysocks==1.7.1 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
pyyaml==6.0.1 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
pyyaml==6.0.2 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
retry==0.9.2 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606 \
--hash=sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4
setuptools==70.3.0 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
watchdog==5.0.3 ; python_version >= "3.12" and python_version < "4.0" \
--hash=sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7 \
--hash=sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1 \
--hash=sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176 \
--hash=sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c \
--hash=sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e \
--hash=sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97 \
--hash=sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05 \
--hash=sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926 \
--hash=sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45 \
--hash=sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e \
--hash=sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb \
--hash=sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b \
--hash=sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8 \
--hash=sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3 \
--hash=sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c \
--hash=sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea \
--hash=sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7 \
--hash=sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490 \
--hash=sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221 \
--hash=sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8 \
--hash=sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7 \
--hash=sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2 \
--hash=sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906 \
--hash=sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627 \
--hash=sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49 \
--hash=sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e \
--hash=sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91 \
--hash=sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b \
--hash=sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9 \
--hash=sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818
29 changes: 14 additions & 15 deletions socks_router/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

import click

from typing import Optional
from typing import Optional, cast

from click_option_group import optgroup, MutuallyExclusiveOptionGroup

from pyaml_env import parse_config


from socks_router.parsers import routing_table
from socks_router.models import ApplicationContext, RetryOptions
from socks_router.models import ApplicationContext, RetryOptions, RoutingTable
from socks_router.router import SocksRouter, SocksRouterRequestHandler
from socks_router.proxies import Proxy, observer, create_proxy

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -61,15 +61,14 @@ def cli(
if os.path.exists(logging_config):
logging.config.dictConfig(parse_config(logging_config))

if routes is None and routes_file.exists():
routes = routes_file.read_text()

with SocksRouter(
(hostname, port),
SocksRouterRequestHandler,
context=ApplicationContext(
routing_table=routing_table.parse(routes or ""),
proxy_retry_options=RetryOptions(tries=retries),
),
) as server:
server.serve_forever()
routing_table_proxy = create_proxy(routes, routes_file, parser=routing_table.parse, default="")
with observer(cast(Proxy[RoutingTable], routing_table_proxy)):
with SocksRouter(
(hostname, port),
SocksRouterRequestHandler,
context=ApplicationContext(
routing_table=routing_table_proxy,
proxy_retry_options=RetryOptions(tries=retries),
),
) as server:
server.serve_forever()
110 changes: 110 additions & 0 deletions socks_router/proxies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import os
import threading
import pathlib
import contextlib
import logging

from typing import Optional, Protocol, runtime_checkable, cast
from collections.abc import Callable, Generator

from dataclasses import dataclass, field
from watchdog.events import LoggingEventHandler, FileSystemEvent, DirModifiedEvent, FileModifiedEvent
from watchdog.observers import ObserverType, Observer
from watchdog.observers.api import BaseObserver

logger = logging.getLogger(__name__)

type Parser[T, S] = Callable[[S], T]
type ModifiedEvent = DirModifiedEvent | FileModifiedEvent


@runtime_checkable
class Proxy[T](Protocol):
__subject__: T


class BaseProxy[T](Proxy[T]):
def __getattr__(self, name):
return getattr(self.__subject__, name)

def __getitem__(self, key):
assert hasattr(self.__subject__, "__getitem__")
return self.__subject__[key]

@classmethod
def create[**P](cls, *args: P.args, **kwargs: P.kwargs) -> T:
return cast(T, cls(*args, **kwargs))


@dataclass
class FileProxy[T](BaseProxy[T], LoggingEventHandler):
path: str
parser: Parser[T, str]
mutex: threading.Lock = field(default_factory=threading.Lock)
__subject__: T = field(init=False)

def __post_init__(self):
LoggingEventHandler.__init__(self, logger=logging.getLogger(type(self).__name__))
self.update()

def __hash__(self):
return hash((self.path, self.parser))

@property
def event_filter(self) -> list[type[FileSystemEvent]]:
return [FileModifiedEvent]

@property
def content(self) -> str:
return pathlib.Path(self.path).read_text()

def update(self):
with self.mutex:
self.__subject__ = self.parser(self.content)

def on_modified(self, event: ModifiedEvent):
super().on_modified(event)

match event:
case FileModifiedEvent() if os.fsdecode(event.src_path) == os.path.realpath(self.path):
self.update()
case _: # pragma: not covered
pass


@dataclass
class LiteralProxy[T, S](BaseProxy[T]):
content: S
parser: Parser[T, S] = lambda content: content # type: ignore[assignment,return-value]
__subject__: T = field(init=False)

def __post_init__(self):
self.__subject__ = self.parser(self.content)


@contextlib.contextmanager
def observer[T](
*proxies: Proxy[T],
cls: ObserverType = Observer,
) -> Generator[BaseObserver]:
observer = cls()
for proxy in proxies:
if isinstance(proxy, FileProxy):
observer.schedule(proxy, os.path.dirname(proxy.path), recursive=True, event_filter=proxy.event_filter)

try:
observer.start()
yield observer
finally:
observer.stop()
observer.join()


def create_proxy[T](content: Optional[str], path: os.PathLike, parser: Parser[T, str], default: str) -> T:
if content is not None:
return LiteralProxy.create(content, parser=parser)

if (file := pathlib.Path(path)).is_file():
return FileProxy.create(f"{file}", parser=parser)

return LiteralProxy.create(default, parser=parser)
9 changes: 9 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def it_should_not_fail(mocker, runner, logging_config, filename):
assert not result.exception
assert result.exit_code == 0

def when_routes_literal_present():
def it_should_not_fail(mocker, runner):
mocker.patch("socks_router.cli.SocksRouter.serve_forever", side_effect=SystemExit(0))

with runner.isolated_filesystem():
result = runner.invoke(cli, ["--routes=''"])
assert not result.exception
assert result.exit_code == 0

def when_routes_file_present():
@pytest.mark.parametrize(
"filename",
Expand Down
15 changes: 15 additions & 0 deletions tests/test_proxies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from socks_router.proxies import LiteralProxy


def describe_proxies():
def describe_LiteralProxy():
def it_should_passthrough():
# given
original: dict[str, str] = {
"foo": "bar",
}
proxy: dict[str, str] = LiteralProxy.create(original)
assert proxy["foo"] == "bar"

original["foo"] = "baz"
assert proxy["foo"] == "baz"
Loading
Loading