Skip to content

Commit cc1c6fc

Browse files
authored
Remove python-dateutil and readerwriterlock dependencies (#976)
Remove some lightly used dependencies and replacing them with stdlib replacements: - `python-dateutil` was only used for parsing `#inst "..."` tags, which was already supported via `datetime.datetime.fromisoformat`, so removing it for now. - `readerwriterlock` was intended to be a reader-preferring lock for things like Atoms, but it's not been updated in some time and probably wasn't making much of a difference to begin with since Python still has a GIL.
1 parent 133b19c commit cc1c6fc

File tree

10 files changed

+72
-70
lines changed

10 files changed

+72
-70
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Changed
1212
* Improved on the nREPL server exception messages by matching that of the REPL user friendly format (#968)
1313

14+
### Removed
15+
* Removed `python-dateutil` and `readerwriterlock` as dependencies, switching to standard library components instead (#976)
16+
1417
### Other
1518
* Run PyPy CI checks on Github Actions rather than CircleCI (#971)
1619

pyproject.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,13 @@ include = ["README.md", "LICENSE"]
3333
python = "^3.8"
3434
attrs = ">=20.2.0"
3535
immutables = ">=0.20,<1.0.0"
36-
prompt-toolkit = "^3.0.0"
36+
prompt-toolkit = ">=3.0.0,<4.0.0"
3737
pyrsistent = ">=0.18.0,<1.0.0"
38-
python-dateutil = "^2.8.1"
39-
readerwriterlock = "^1.0.8"
40-
typing-extensions = "^4.7.0"
38+
typing-extensions = ">=4.7.0,<5.0.0"
4139

4240
astor = { version = "^0.8.1", python = "<3.9", optional = true }
43-
pytest = { version = "^7.0.0", optional = true }
44-
pygments = { version = "^2.9.0", optional = true }
41+
pytest = { version = ">=7.0.0,<9.0.0", optional = true }
42+
pygments = { version = ">=2.9.0,<3.0.0", optional = true }
4543

4644
[tool.poetry.group.dev.dependencies]
4745
black = ">=24.0.0"
@@ -51,7 +49,7 @@ docutils = [
5149
]
5250
isort = "*"
5351
pygments = "*"
54-
pytest = "^7.0.0"
52+
pytest = ">=7.0.0,<9.0.0"
5553
pytest-pycharm = "*"
5654
# Ensure the Sphinx version remains synchronized with docs/requirements.txt
5755
# to maintain consistent output during both development and publishing on

src/basilisp/lang/atom.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import threading
12
from typing import Callable, Generic, Optional, TypeVar
23

3-
from readerwriterlock.rwlock import RWLockFair
44
from typing_extensions import Concatenate, ParamSpec
55

66
from basilisp.lang.interfaces import IPersistentMap, RefValidator
@@ -22,15 +22,15 @@ def __init__(
2222
) -> None:
2323
self._meta: Optional[IPersistentMap] = meta
2424
self._state = state
25-
self._lock = RWLockFair()
25+
self._lock = threading.RLock()
2626
self._watches = PersistentMap.empty()
2727
self._validator = validator
2828

2929
if validator is not None:
3030
self._validate(state)
3131

3232
def _compare_and_set(self, old: T, new: T) -> bool:
33-
with self._lock.gen_wlock():
33+
with self._lock:
3434
if self._state != old:
3535
return False
3636
self._state = new
@@ -48,7 +48,7 @@ def compare_and_set(self, old: T, new: T) -> bool:
4848

4949
def deref(self) -> T:
5050
"""Return the state stored within the Atom."""
51-
with self._lock.gen_rlock():
51+
with self._lock:
5252
return self._state
5353

5454
def reset(self, v: T) -> T:

src/basilisp/lang/reference.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import threading
12
from typing import Any, Callable, Optional, TypeVar
23

3-
from readerwriterlock.rwlock import RWLockable
44
from typing_extensions import Concatenate, ParamSpec
55

66
from basilisp.lang import keyword as kw
@@ -27,21 +27,21 @@ class ReferenceBase(IReference):
2727
2828
Implementers must have the `_lock` and `_meta` properties defined."""
2929

30-
_lock: RWLockable
30+
_lock: threading.RLock
3131
_meta: Optional[IPersistentMap]
3232

3333
@property
3434
def meta(self) -> Optional[IPersistentMap]:
35-
with self._lock.gen_rlock():
35+
with self._lock:
3636
return self._meta
3737

3838
def alter_meta(self, f: AlterMeta, *args) -> Optional[IPersistentMap]:
39-
with self._lock.gen_wlock():
39+
with self._lock:
4040
self._meta = f(self._meta, *args)
4141
return self._meta
4242

4343
def reset_meta(self, meta: Optional[IPersistentMap]) -> Optional[IPersistentMap]:
44-
with self._lock.gen_wlock():
44+
with self._lock:
4545
self._meta = meta
4646
return meta
4747

@@ -63,7 +63,7 @@ class RefBase(IRef[T], ReferenceBase):
6363
_watches: IPersistentMap[RefWatchKey, RefWatcher[T]]
6464

6565
def add_watch(self, k: RefWatchKey, wf: RefWatcher[T]) -> "RefBase[T]":
66-
with self._lock.gen_wlock():
66+
with self._lock:
6767
self._watches = self._watches.assoc(k, wf)
6868
return self
6969

@@ -72,26 +72,18 @@ def _notify_watches(self, old: T, new: T) -> None:
7272
wf(k, self, old, new)
7373

7474
def remove_watch(self, k: RefWatchKey) -> "RefBase[T]":
75-
with self._lock.gen_wlock():
75+
with self._lock:
7676
self._watches = self._watches.dissoc(k)
7777
return self
7878

7979
def get_validator(self) -> Optional[RefValidator[T]]:
8080
return self._validator
8181

8282
def set_validator(self, vf: Optional[RefValidator[T]] = None) -> None:
83-
# We cannot use a write lock here since we're calling `self.deref()` which
84-
# attempts to acquire the read lock for the Ref and will deadlock if the
85-
# lock is not reentrant.
86-
#
87-
# There are no guarantees that the Ref lock is reentrant and the default
88-
# locks for Atoms and Vars are not).
89-
#
90-
# This is probably ok for most cases since we expect contention is low or
91-
# non-existent while setting a validator function.
92-
if vf is not None:
93-
self._validate(self.deref(), vf=vf)
94-
self._validator = vf
83+
with self._lock:
84+
if vf is not None:
85+
self._validate(self.deref(), vf=vf)
86+
self._validator = vf
9587

9688
def _validate(self, val: Any, vf: Optional[RefValidator[T]] = None) -> None:
9789
vf = vf or self._validator

src/basilisp/lang/runtime.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
cast,
3838
)
3939

40-
from readerwriterlock.rwlock import RWLockFair
41-
4240
from basilisp.lang import keyword as kw
4341
from basilisp.lang import list as llist
4442
from basilisp.lang import map as lmap
@@ -266,7 +264,7 @@ def __init__(
266264
self._is_bound = False
267265
self._tl = None
268266
self._meta = meta
269-
self._lock = RWLockFair()
267+
self._lock = threading.RLock()
270268
self._watches = lmap.PersistentMap.empty()
271269
self._validator = None
272270

@@ -300,7 +298,7 @@ def dynamic(self) -> bool:
300298
return self._dynamic
301299

302300
def set_dynamic(self, dynamic: bool) -> None:
303-
with self._lock.gen_wlock():
301+
with self._lock:
304302
if dynamic == self._dynamic:
305303
return
306304

@@ -328,18 +326,18 @@ def _set_root(self, newval) -> None:
328326

329327
@property
330328
def root(self):
331-
with self._lock.gen_rlock():
329+
with self._lock:
332330
return self._root
333331

334332
def bind_root(self, val) -> None:
335333
"""Atomically update the root binding of this Var to val."""
336-
with self._lock.gen_wlock():
334+
with self._lock:
337335
self._set_root(val)
338336

339337
def alter_root(self, f, *args) -> None:
340338
"""Atomically alter the root binding of this Var to the result of calling
341339
f with the existing root value and any additional arguments."""
342-
with self._lock.gen_wlock():
340+
with self._lock:
343341
self._set_root(f(self._root, *args))
344342

345343
def push_bindings(self, val):
@@ -366,7 +364,7 @@ def value(self):
366364
367365
For non-dynamic Vars, this will just be the root. For dynamic Vars, this will
368366
be any thread-local binding if one is defined. Otherwise, the root value."""
369-
with self._lock.gen_rlock():
367+
with self._lock:
370368
if self._dynamic:
371369
assert self._tl is not None
372370
if len(self._tl.bindings) > 0:
@@ -378,7 +376,7 @@ def set_value(self, v) -> None:
378376
379377
If the Var is not dynamic, this is equivalent to binding the root value. If the
380378
Var is dynamic, this will set the thread-local bindings for the Var."""
381-
with self._lock.gen_wlock():
379+
with self._lock:
382380
if self._dynamic:
383381
assert self._tl is not None
384382
self._validate(v)
@@ -585,7 +583,7 @@ def __init__(
585583
self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
586584

587585
self._meta: Optional[IPersistentMap] = None
588-
self._lock = RWLockFair()
586+
self._lock = threading.RLock()
589587

590588
self._aliases: NamespaceMap = lmap.PersistentMap.empty()
591589
self._imports: ModuleMap = lmap.map(
@@ -621,36 +619,36 @@ def module(self, m: BasilispModule):
621619
def aliases(self) -> NamespaceMap:
622620
"""A mapping between a symbolic alias and another Namespace. The
623621
fully qualified name of a namespace is also an alias for itself."""
624-
with self._lock.gen_rlock():
622+
with self._lock:
625623
return self._aliases
626624

627625
@property
628626
def imports(self) -> ModuleMap:
629627
"""A mapping of names to Python modules imported into the current
630628
namespace."""
631-
with self._lock.gen_rlock():
629+
with self._lock:
632630
return self._imports
633631

634632
@property
635633
def import_aliases(self) -> AliasMap:
636634
"""A mapping of a symbolic alias and a Python module name."""
637-
with self._lock.gen_rlock():
635+
with self._lock:
638636
return self._import_aliases
639637

640638
@property
641639
def interns(self) -> VarMap:
642640
"""A mapping between a symbolic name and a Var. The Var may point to
643641
code, data, or nothing, if it is unbound. Vars in `interns` are
644642
interned in _this_ namespace."""
645-
with self._lock.gen_rlock():
643+
with self._lock:
646644
return self._interns
647645

648646
@property
649647
def refers(self) -> VarMap:
650648
"""A mapping between a symbolic name and a Var. Vars in refers are
651649
interned in another namespace and are only referred to without an
652650
alias in this namespace."""
653-
with self._lock.gen_rlock():
651+
with self._lock:
654652
return self._refers
655653

656654
def __repr__(self):
@@ -681,41 +679,41 @@ def require(self, ns_name: str, *aliases: sym.Symbol) -> BasilispModule:
681679

682680
def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None:
683681
"""Add Symbol aliases for the given Namespace."""
684-
with self._lock.gen_wlock():
682+
with self._lock:
685683
new_m = self._aliases
686684
for alias in aliases:
687685
new_m = new_m.assoc(alias, namespace)
688686
self._aliases = new_m
689687

690688
def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
691689
"""Get the Namespace aliased by Symbol or None if it does not exist."""
692-
with self._lock.gen_rlock():
690+
with self._lock:
693691
return self._aliases.val_at(alias, None)
694692

695693
def remove_alias(self, alias: sym.Symbol) -> None:
696694
"""Remove the Namespace aliased by Symbol. Return None."""
697-
with self._lock.gen_wlock():
695+
with self._lock:
698696
self._aliases = self._aliases.dissoc(alias)
699697

700698
def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
701699
"""Intern the Var given in this namespace mapped by the given Symbol.
702700
If the Symbol already maps to a Var, this method _will not overwrite_
703701
the existing Var mapping unless the force keyword argument is given
704702
and is True."""
705-
with self._lock.gen_wlock():
703+
with self._lock:
706704
old_var = self._interns.val_at(sym, None)
707705
if old_var is None or force:
708706
self._interns = self._interns.assoc(sym, var)
709707
return self._interns.val_at(sym)
710708

711709
def unmap(self, sym: sym.Symbol) -> None:
712-
with self._lock.gen_wlock():
710+
with self._lock:
713711
self._interns = self._interns.dissoc(sym)
714712

715713
def find(self, sym: sym.Symbol) -> Optional[Var]:
716714
"""Find Vars mapped by the given Symbol input or None if no Vars are
717715
mapped by that Symbol."""
718-
with self._lock.gen_rlock():
716+
with self._lock:
719717
v = self._interns.val_at(sym, None)
720718
if v is None:
721719
return self._refers.val_at(sym, None)
@@ -724,7 +722,7 @@ def find(self, sym: sym.Symbol) -> Optional[Var]:
724722
def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None:
725723
"""Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
726724
the aliases will be applied to the"""
727-
with self._lock.gen_wlock():
725+
with self._lock:
728726
self._imports = self._imports.assoc(sym, module)
729727
if aliases:
730728
m = self._import_aliases
@@ -738,7 +736,7 @@ def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
738736
739737
First try to resolve a module directly with the given name. If no module
740738
can be resolved, attempt to resolve the module using import aliases."""
741-
with self._lock.gen_rlock():
739+
with self._lock:
742740
mod = self._imports.val_at(sym, None)
743741
if mod is None:
744742
alias = self._import_aliases.get(sym, None)
@@ -750,17 +748,17 @@ def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
750748
def add_refer(self, sym: sym.Symbol, var: Var) -> None:
751749
"""Refer var in this namespace under the name sym."""
752750
if not var.is_private:
753-
with self._lock.gen_wlock():
751+
with self._lock:
754752
self._refers = self._refers.assoc(sym, var)
755753

756754
def get_refer(self, sym: sym.Symbol) -> Optional[Var]:
757755
"""Get the Var referred by Symbol or None if it does not exist."""
758-
with self._lock.gen_rlock():
756+
with self._lock:
759757
return self._refers.val_at(sym, None)
760758

761759
def refer_all(self, other_ns: "Namespace") -> None:
762760
"""Refer all the Vars in the other namespace."""
763-
with self._lock.gen_wlock():
761+
with self._lock:
764762
final_refers = self._refers
765763
for s, var in other_ns.interns.items():
766764
if not var.is_private:

src/basilisp/lang/util.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
from fractions import Fraction
99
from typing import Iterable, Match, Pattern, Type
1010

11-
from dateutil import parser as dateparser
12-
1311
from basilisp.lang import atom as atom
1412

1513
_DOUBLE_DOT = ".."
@@ -259,7 +257,7 @@ def fraction(numerator: int, denominator: int) -> Fraction:
259257

260258
def inst_from_str(inst_str: str) -> datetime.datetime:
261259
"""Create a datetime instance from an RFC 3339 formatted date string."""
262-
return dateparser.parse(inst_str)
260+
return datetime.datetime.fromisoformat(inst_str)
263261

264262

265263
def regex_from_str(regex_str: str) -> Pattern:

0 commit comments

Comments
 (0)