Skip to content

Commit 8efe253

Browse files
committed
Async driver
3 parents 10c1009 + 1e822f3 + fe6cced commit 8efe253

File tree

201 files changed

+7047
-4950
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+7047
-4950
lines changed

.editorconfig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# top-most EditorConfig file
2+
root = true
3+
4+
# Unix-style newlines with a newline ending every file
5+
[*]
6+
end_of_line = lf
7+
insert_final_newline = true
8+
charset = utf-8
9+
10+
[*.{py,js,rst,txt,sh,bat}]
11+
trim_trailing_whitespace = true
12+
13+
[{Makefile,Drockerfile}]
14+
trim_trailing_whitespace = true
15+
16+
[*.bat]
17+
end_of_line =crlf
18+
19+
[*.py]
20+
max_line_length = 79
21+
indent_style = space
22+
indent_size = 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ neo4j-enterprise-*
2929
*.so
3030

3131
testkit/CAs
32+
testkit/CustomCAs

.pre-commit-config.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
repos:
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v4.0.1
6+
hooks:
7+
- id: check-case-conflict
8+
- id: check-docstring-first
9+
- id: check-symlinks
10+
- id: destroyed-symlinks
11+
- id: end-of-file-fixer
12+
exclude_types:
13+
- image
14+
- id: fix-encoding-pragma
15+
args: [ --remove ]
16+
- id: mixed-line-ending
17+
args: [ --fix=lf ]
18+
exclude_types:
19+
- batch
20+
- id: trailing-whitespace
21+
args: [ --markdown-linebreak-ext=md ]
22+
# - repo: https://github.com/pycqa/flake8
23+
# rev: 4.0.1
24+
# hooks:
25+
# - id: flake8
26+
# additional_dependencies:
27+
# - flake8-bugbear
28+
# - flake8-builtins
29+
# - flake8-docstrings
30+
# - flake8-quotes
31+
# - pep8-naming
32+
- repo: https://github.com/pycqa/isort
33+
rev: 5.10.0
34+
hooks:
35+
- id: isort
36+
- repo: local
37+
hooks:
38+
- id: unasync
39+
name: unasync
40+
entry: bin/make-unasync
41+
language: system
42+
files: "^(neo4j/_async|tests/unit/async_|testkitbackend/_async)/.*"

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
- Python 3.10 support added
66
- Python 3.6 support has been dropped.
7-
7+
- `Result`, `Session`, and `Transaction`, can no longer be imported from
8+
`neo4j.work`. They should've been imported from `neo4j` all along.
9+
- Experimental pipelines feature has been removed.
10+
- Experimental async driver has been added.
811

912
## Version 4.4
1013

CONTRIBUTING.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,45 @@ Occasionally, we might also have logistical, commercial, or legal reasons why we
5151
Remember that many community members have become regular contributors and some are now even Neo employees!
5252

5353

54+
## Specifically for this project:
55+
56+
All code in `_sync` or `sync` folders is auto-generated. Don't change it, but
57+
install the pre-commit hooks as described below insted. They will take care of
58+
updating the code if necessary.
59+
60+
Setting up the development environment:
61+
* Install Python 3.6+
62+
* Install the requirements
63+
```bash
64+
$ python3 -m pip install -U pip
65+
$ python3 -m pip install -Ur requirements-dev.txt
66+
```
67+
* Install the pre-commit hook, that will do some code-format-checking everytime
68+
you commit.
69+
```bash
70+
$ pre-commit install
71+
```
72+
Note that this is not an auto-formatter. It will alter some code, but
73+
mostly it will just complain about non-compliant code.
74+
You can disable a certain check for a single line of code if you think
75+
your code-style if preferable. E.g.
76+
```python
77+
assume_this_line(violates_rule_e123, and_w321) # noqa: E123,W321
78+
```
79+
Or use just `# noqa` to disable all checks for this line.
80+
If you use `# noqa` on its own line, it will disable *all* checks for the
81+
whole file. Don't do that.
82+
To disable certain rules for a whole file, check out
83+
`setup.cfg`.
84+
If you want to run the checks manually, you can do so:
85+
```bash
86+
$ pre-commit run --all-files
87+
# or
88+
$ pre-commit run --file path/to/a/file
89+
```
90+
For more details see [flake8](https://flake8.pycqa.org/).
91+
92+
5493
## Got an idea for a new project?
5594

5695
If you have an idea for a new tool or library, start by talking to other people in the community.

ISSUE_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ If you simply want to get started or have a question on how to use a particular
99
[StackOverflow](http://stackoverflow.com/questions/tagged/neo4j) also hosts a ton of questions and might already have a discussion around your problem.
1010
Make sure you have a look there too.
1111

12-
If you want to make a feature request, please prefix your issue title with `[Feature Request]` so that it is clear to us.
12+
If you want to make a feature request, please prefix your issue title with `[Feature Request]` so that it is clear to us.
1313
If you have a bug report however, please continue reading.
1414
To help us understand your issue, please specify important details, primarily:
1515

bin/make-unasync

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright (c) "Neo4j"
4+
# Neo4j Sweden AB [http://neo4j.com]
5+
#
6+
# This file is part of Neo4j.
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
21+
import collections
22+
import errno
23+
from functools import reduce
24+
import os
25+
from pathlib import Path
26+
import sys
27+
import tokenize as std_tokenize
28+
29+
import isort
30+
import isort.files
31+
import unasync
32+
33+
34+
ROOT_DIR = Path(__file__).parents[1].absolute()
35+
ASYNC_DIR = ROOT_DIR / "neo4j" / "_async"
36+
SYNC_DIR = ROOT_DIR / "neo4j" / "_sync"
37+
ASYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "async_"
38+
SYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "sync"
39+
ASYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_async"
40+
SYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_sync"
41+
UNASYNC_SUFFIX = ".unasync"
42+
43+
PY_FILE_EXTENSIONS = {".py", ".pyi"}
44+
45+
46+
# copy from unasync for customization -----------------------------------------
47+
# https://github.com/python-trio/unasync
48+
# License: MIT and Apache2
49+
50+
51+
Token = collections.namedtuple(
52+
"Token", ["type", "string", "start", "end", "line"]
53+
)
54+
55+
56+
def _makedirs_existok(dir):
57+
try:
58+
os.makedirs(dir)
59+
except OSError as e:
60+
if e.errno != errno.EEXIST:
61+
raise
62+
63+
64+
def _get_tokens(f):
65+
if sys.version_info[0] == 2:
66+
for tok in std_tokenize.generate_tokens(f.readline):
67+
type_, string, start, end, line = tok
68+
yield Token(type_, string, start, end, line)
69+
else:
70+
for tok in std_tokenize.tokenize(f.readline):
71+
if tok.type == std_tokenize.ENCODING:
72+
continue
73+
yield tok
74+
75+
76+
def _tokenize(f):
77+
last_end = (1, 0)
78+
for tok in _get_tokens(f):
79+
if last_end[0] < tok.start[0]:
80+
yield "", std_tokenize.STRING, " \\\n"
81+
last_end = (tok.start[0], 0)
82+
83+
space = ""
84+
if tok.start > last_end:
85+
assert tok.start[0] == last_end[0]
86+
space = " " * (tok.start[1] - last_end[1])
87+
yield space, tok.type, tok.string
88+
89+
last_end = tok.end
90+
if tok.type in [std_tokenize.NEWLINE, std_tokenize.NL]:
91+
last_end = (tok.end[0] + 1, 0)
92+
93+
94+
def _untokenize(tokens):
95+
return "".join(space + tokval for space, tokval in tokens)
96+
97+
98+
# end of copy -----------------------------------------------------------------
99+
100+
101+
class CustomRule(unasync.Rule):
102+
def __init__(self, *args, **kwargs):
103+
super(CustomRule, self).__init__(*args, **kwargs)
104+
self.out_files = []
105+
106+
def _unasync_name(self, name):
107+
# copy from unasync to customize renaming rules
108+
# https://github.com/python-trio/unasync
109+
# License: MIT and Apache2
110+
if name in self.token_replacements:
111+
return self.token_replacements[name]
112+
# Convert class names from 'AsyncXyz' to 'Xyz'
113+
elif len(name) > 5 and name.startswith("Async") and name[5].isupper():
114+
return name[5:]
115+
# Convert variable/method/function names from 'async_xyz' to 'xyz'
116+
elif len(name) > 6 and name.startswith("async_"):
117+
return name[6:]
118+
return name
119+
120+
def _unasync_file(self, filepath):
121+
# copy from unasync to append file suffix to out path
122+
# https://github.com/python-trio/unasync
123+
# License: MIT and Apache2
124+
with open(filepath, "rb") as f:
125+
write_kwargs = {}
126+
if sys.version_info[0] >= 3:
127+
encoding, _ = std_tokenize.detect_encoding(f.readline)
128+
write_kwargs["encoding"] = encoding
129+
f.seek(0)
130+
tokens = _tokenize(f)
131+
tokens = self._unasync_tokens(tokens)
132+
result = _untokenize(tokens)
133+
outfile_path = filepath.replace(self.fromdir, self.todir)
134+
outfile_path += UNASYNC_SUFFIX
135+
self.out_files.append(outfile_path)
136+
_makedirs_existok(os.path.dirname(outfile_path))
137+
with open(outfile_path, "w", **write_kwargs) as f:
138+
print(result, file=f, end="")
139+
140+
141+
def apply_unasync(files):
142+
"""Generate sync code from async code."""
143+
144+
additional_main_replacements = {}
145+
additional_test_replacements = {
146+
"_async": "_sync",
147+
"mark_async_test": "mark_sync_test",
148+
}
149+
additional_testkit_backend_replacements = {}
150+
rules = [
151+
CustomRule(
152+
fromdir=str(ASYNC_DIR),
153+
todir=str(SYNC_DIR),
154+
additional_replacements=additional_main_replacements,
155+
),
156+
CustomRule(
157+
fromdir=str(ASYNC_TEST_DIR),
158+
todir=str(SYNC_TEST_DIR),
159+
additional_replacements=additional_test_replacements,
160+
),
161+
CustomRule(
162+
fromdir=str(ASYNC_TESTKIT_BACKEND_DIR),
163+
todir=str(SYNC_TESTKIT_BACKEND_DIR),
164+
additional_replacements=additional_testkit_backend_replacements,
165+
),
166+
]
167+
168+
if not files:
169+
paths = list(ASYNC_DIR.rglob("*"))
170+
paths += list(ASYNC_TEST_DIR.rglob("*"))
171+
paths += list(ASYNC_TESTKIT_BACKEND_DIR.rglob("*"))
172+
else:
173+
paths = [ROOT_DIR / Path(f) for f in files]
174+
filtered_paths = []
175+
for path in paths:
176+
if path.suffix in PY_FILE_EXTENSIONS:
177+
filtered_paths.append(path)
178+
179+
unasync.unasync_files(map(str, filtered_paths), rules)
180+
181+
return [Path(path) for rule in rules for path in rule.out_files]
182+
183+
184+
def apply_isort(paths):
185+
"""Sort imports in generated sync code.
186+
187+
Since classes in imports are renamed from AsyncXyz to Xyz, the alphabetical
188+
order of the import can change.
189+
"""
190+
isort_config = isort.Config(settings_path=str(ROOT_DIR), quiet=True)
191+
192+
for path in paths:
193+
isort.file(str(path), config=isort_config)
194+
195+
return paths
196+
197+
198+
def apply_changes(paths):
199+
def files_equal(path1, path2):
200+
with open(path1, "rb") as f1:
201+
with open(path2, "rb") as f2:
202+
data1 = f1.read(1024)
203+
data2 = f2.read(1024)
204+
while data1 or data2:
205+
if data1 != data2:
206+
changed_paths[path1] = path2
207+
return False
208+
data1 = f1.read(1024)
209+
data2 = f2.read(1024)
210+
return True
211+
212+
changed_paths = {}
213+
214+
for in_path in paths:
215+
out_path = Path(str(in_path)[:-len(UNASYNC_SUFFIX)])
216+
if not out_path.is_file():
217+
changed_paths[in_path] = out_path
218+
continue
219+
if not files_equal(in_path, out_path):
220+
changed_paths[in_path] = out_path
221+
continue
222+
in_path.unlink()
223+
224+
for in_path, out_path in changed_paths.items():
225+
in_path.replace(out_path)
226+
227+
return list(changed_paths.values())
228+
229+
230+
def main():
231+
files = None
232+
if len(sys.argv) >= 1:
233+
files = sys.argv[1:]
234+
paths = apply_unasync(files)
235+
paths = apply_isort(paths)
236+
changed_paths = apply_changes(paths)
237+
238+
if changed_paths:
239+
for path in changed_paths:
240+
print("Updated " + str(path))
241+
exit(1)
242+
243+
244+
if __name__ == "__main__":
245+
main()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<!-- Adds target=_blank to external links -->
22
$(document).ready(function () {
33
$('a[href^="http://"], a[href^="https://"]').not('a[class*=internal]').attr('target', '_blank');
4-
});
4+
});

0 commit comments

Comments
 (0)