beam-lan 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .DS_Store
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ - `beam recv CODE` finds the sender on the local network by itself; no IP
6
+ address is needed. Use `--host IP[:PORT]` to connect directly.
7
+ - New `--wait SEC` option for how long the receiver searches.
8
+ - New `beam.find_sender(code)` library function. `receive_project` takes
9
+ `host=None` to discover the sender.
10
+ - Fix: sending crashed on Python 3.9–3.11 (the receiver saw an empty stream).
11
+ - Breaking: `beam recv HOST --code CODE` is now `beam recv CODE --host HOST`.
12
+
13
+ ## 0.1.0
14
+
15
+ - First release: `beam send` / `beam recv` with excludes, `.beamignore`,
16
+ SHA-256 verification and streamed gzip transfer.
beam_lan-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nylonxd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.5
2
+ Name: beam-lan
3
+ Version: 0.2.0
4
+ Summary: Move a project folder from one laptop to another over the local network.
5
+ Project-URL: Homepage, https://github.com/NYLONXD/beam
6
+ Project-URL: Source, https://github.com/NYLONXD/beam
7
+ Project-URL: Issues, https://github.com/NYLONXD/beam/issues
8
+ Project-URL: Changelog, https://github.com/NYLONXD/beam/blob/main/CHANGELOG.md
9
+ Author-email: Nylonxd <Himanshujha202005@gmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Nylonxd
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: cli,file-transfer,lan,local-network,project,sync,tar
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Environment :: Console
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3 :: Only
40
+ Classifier: Programming Language :: Python :: 3.9
41
+ Classifier: Programming Language :: Python :: 3.10
42
+ Classifier: Programming Language :: Python :: 3.11
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Programming Language :: Python :: 3.13
45
+ Classifier: Programming Language :: Python :: 3.14
46
+ Classifier: Topic :: Communications :: File Sharing
47
+ Classifier: Topic :: System :: Archiving
48
+ Classifier: Typing :: Typed
49
+ Requires-Python: >=3.9
50
+ Provides-Extra: dev
51
+ Requires-Dist: build; extra == 'dev'
52
+ Requires-Dist: pytest>=7; extra == 'dev'
53
+ Requires-Dist: ruff>=0.4; extra == 'dev'
54
+ Requires-Dist: twine; extra == 'dev'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # beam
58
+
59
+ Move a project folder from one laptop to another over the local network.
60
+ Pure Python standard library, no runtime dependencies, works on Windows,
61
+ macOS and Linux (Python 3.9+).
62
+
63
+ ```bash
64
+ # laptop A
65
+ beam send ~/thesis
66
+
67
+ # laptop B
68
+ beam recv ab3f9c
69
+ ```
70
+
71
+ ## Install
72
+
73
+ Both laptops need it:
74
+
75
+ ```bash
76
+ pip install beam-lan
77
+ ```
78
+
79
+ `pipx install beam-lan` also works and keeps it out of your other environments.
80
+
81
+ If your shell says `beam` is not a recognised command, pip put the script in a
82
+ folder that is not on your `PATH` (pip prints a warning naming it). Either add
83
+ that folder to `PATH`, or run `python -m beam` instead of `beam`.
84
+
85
+ ## Use it
86
+
87
+ On the laptop that has the project:
88
+
89
+ ```bash
90
+ beam send ~/thesis
91
+ ```
92
+
93
+ It prints a short one-time code. Send it to the other laptop however you like
94
+ (chat, email, read it out), then run there:
95
+
96
+ ```bash
97
+ beam recv ab3f9c
98
+ ```
99
+
100
+ The receiver finds the sender on the local network by itself, so no IP address
101
+ is needed. Both machines must be on the same network.
102
+
103
+ ## As a library
104
+
105
+ ```python
106
+ import beam
107
+
108
+ beam.send_project("~/thesis", code="ab3f9c") # laptop A
109
+ beam.receive_project(code="ab3f9c", out="~/work") # laptop B
110
+ ```
111
+
112
+ `receive_project` also takes `host=` to skip discovery. `send_project` accepts
113
+ `port=0` to bind a free port, and an `on_ready` callback that receives the bound
114
+ host, port and code once it is listening. `beam.find_sender(code)` returns the
115
+ `(host, port, name)` of a sender without transferring anything.
116
+
117
+ ## What it does
118
+
119
+ - Skips `.venv`, `__pycache__`, `.git`, `node_modules` and similar by default.
120
+ Copying a virtualenv between machines mostly produces broken paths; rebuild
121
+ it from `requirements.txt` instead.
122
+ - Hashes every file before sending and re-hashes after extraction, so
123
+ corruption is reported instead of discovered weeks later.
124
+ - Streams a gzipped tar straight through the socket, so no temporary archive
125
+ is written on either side.
126
+ - A `.beamignore` file in the project root adds per-project glob patterns, one
127
+ per line (`#` starts a comment).
128
+
129
+ ## Options
130
+
131
+ `beam send PATH`
132
+
133
+ | Flag | Effect |
134
+ | --- | --- |
135
+ | `--code CODE` | choose the code instead of a random one |
136
+ | `--port PORT` | TCP port to listen on (default 8765) |
137
+ | `--exclude GLOB` | extra pattern to skip, repeatable |
138
+ | `--all` | ignore the default exclude list |
139
+ | `--max-size MB` | skip files larger than this |
140
+ | `--no-hash` | size-check only, faster on large datasets |
141
+ | `--fast` | lower compression, starts sooner |
142
+
143
+ `beam recv CODE`
144
+
145
+ | Flag | Effect |
146
+ | --- | --- |
147
+ | `--host IP[:PORT]` | connect directly instead of searching the network |
148
+ | `--wait SEC` | how long to search for the sender (default 15) |
149
+ | `--out DIR` | where to write the folder (default: current directory) |
150
+ | `--name NAME` | rename the folder on arrival |
151
+ | `--force` | write into a non-empty destination |
152
+ | `--no-verify` | skip the checksum pass after extraction |
153
+
154
+ ## Troubleshooting
155
+
156
+ **`no sender with code ... found`**
157
+
158
+ - Check both laptops are on the same Wi-Fi or wired network. Guest and office
159
+ networks often isolate devices from each other or drop broadcasts.
160
+ - On Windows, allow Python through the firewall when it asks on the sending
161
+ laptop. If you dismissed the prompt, allow `python.exe` for private networks
162
+ in *Windows Defender Firewall → Allow an app*.
163
+ - Skip discovery and connect directly using the address the sender prints:
164
+ `beam recv ab3f9c --host 192.168.1.42:8765`.
165
+
166
+ Discovery uses UDP port 8766 and the transfer uses TCP port 8765.
167
+
168
+ ## Limits
169
+
170
+ The connection is plain TCP with no encryption. The code stops casual grabs on
171
+ a shared network; it is not security. Do not use it on networks you do not
172
+ trust. For repeated syncing rather than a one-time move, `rsync -av
173
+ --exclude=.venv` or a private Git remote is a better fit.
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ pip install -e ".[dev]"
179
+ pytest
180
+ ruff check .
181
+ python -m build
182
+ ```
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,130 @@
1
+ # beam
2
+
3
+ Move a project folder from one laptop to another over the local network.
4
+ Pure Python standard library, no runtime dependencies, works on Windows,
5
+ macOS and Linux (Python 3.9+).
6
+
7
+ ```bash
8
+ # laptop A
9
+ beam send ~/thesis
10
+
11
+ # laptop B
12
+ beam recv ab3f9c
13
+ ```
14
+
15
+ ## Install
16
+
17
+ Both laptops need it:
18
+
19
+ ```bash
20
+ pip install beam-lan
21
+ ```
22
+
23
+ `pipx install beam-lan` also works and keeps it out of your other environments.
24
+
25
+ If your shell says `beam` is not a recognised command, pip put the script in a
26
+ folder that is not on your `PATH` (pip prints a warning naming it). Either add
27
+ that folder to `PATH`, or run `python -m beam` instead of `beam`.
28
+
29
+ ## Use it
30
+
31
+ On the laptop that has the project:
32
+
33
+ ```bash
34
+ beam send ~/thesis
35
+ ```
36
+
37
+ It prints a short one-time code. Send it to the other laptop however you like
38
+ (chat, email, read it out), then run there:
39
+
40
+ ```bash
41
+ beam recv ab3f9c
42
+ ```
43
+
44
+ The receiver finds the sender on the local network by itself, so no IP address
45
+ is needed. Both machines must be on the same network.
46
+
47
+ ## As a library
48
+
49
+ ```python
50
+ import beam
51
+
52
+ beam.send_project("~/thesis", code="ab3f9c") # laptop A
53
+ beam.receive_project(code="ab3f9c", out="~/work") # laptop B
54
+ ```
55
+
56
+ `receive_project` also takes `host=` to skip discovery. `send_project` accepts
57
+ `port=0` to bind a free port, and an `on_ready` callback that receives the bound
58
+ host, port and code once it is listening. `beam.find_sender(code)` returns the
59
+ `(host, port, name)` of a sender without transferring anything.
60
+
61
+ ## What it does
62
+
63
+ - Skips `.venv`, `__pycache__`, `.git`, `node_modules` and similar by default.
64
+ Copying a virtualenv between machines mostly produces broken paths; rebuild
65
+ it from `requirements.txt` instead.
66
+ - Hashes every file before sending and re-hashes after extraction, so
67
+ corruption is reported instead of discovered weeks later.
68
+ - Streams a gzipped tar straight through the socket, so no temporary archive
69
+ is written on either side.
70
+ - A `.beamignore` file in the project root adds per-project glob patterns, one
71
+ per line (`#` starts a comment).
72
+
73
+ ## Options
74
+
75
+ `beam send PATH`
76
+
77
+ | Flag | Effect |
78
+ | --- | --- |
79
+ | `--code CODE` | choose the code instead of a random one |
80
+ | `--port PORT` | TCP port to listen on (default 8765) |
81
+ | `--exclude GLOB` | extra pattern to skip, repeatable |
82
+ | `--all` | ignore the default exclude list |
83
+ | `--max-size MB` | skip files larger than this |
84
+ | `--no-hash` | size-check only, faster on large datasets |
85
+ | `--fast` | lower compression, starts sooner |
86
+
87
+ `beam recv CODE`
88
+
89
+ | Flag | Effect |
90
+ | --- | --- |
91
+ | `--host IP[:PORT]` | connect directly instead of searching the network |
92
+ | `--wait SEC` | how long to search for the sender (default 15) |
93
+ | `--out DIR` | where to write the folder (default: current directory) |
94
+ | `--name NAME` | rename the folder on arrival |
95
+ | `--force` | write into a non-empty destination |
96
+ | `--no-verify` | skip the checksum pass after extraction |
97
+
98
+ ## Troubleshooting
99
+
100
+ **`no sender with code ... found`**
101
+
102
+ - Check both laptops are on the same Wi-Fi or wired network. Guest and office
103
+ networks often isolate devices from each other or drop broadcasts.
104
+ - On Windows, allow Python through the firewall when it asks on the sending
105
+ laptop. If you dismissed the prompt, allow `python.exe` for private networks
106
+ in *Windows Defender Firewall → Allow an app*.
107
+ - Skip discovery and connect directly using the address the sender prints:
108
+ `beam recv ab3f9c --host 192.168.1.42:8765`.
109
+
110
+ Discovery uses UDP port 8766 and the transfer uses TCP port 8765.
111
+
112
+ ## Limits
113
+
114
+ The connection is plain TCP with no encryption. The code stops casual grabs on
115
+ a shared network; it is not security. Do not use it on networks you do not
116
+ trust. For repeated syncing rather than a one-time move, `rsync -av
117
+ --exclude=.venv` or a private Git remote is a better fit.
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ pip install -e ".[dev]"
123
+ pytest
124
+ ruff check .
125
+ python -m build
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "beam-lan"
7
+ dynamic = ["version"]
8
+ description = "Move a project folder from one laptop to another over the local network."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Nylonxd", email = "Himanshujha202005@gmail.com" }]
13
+ keywords = ["file-transfer", "lan", "sync", "project", "tar", "cli", "local-network"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Operating System :: OS Independent",
28
+ "Typing :: Typed",
29
+ "Topic :: Communications :: File Sharing",
30
+ "Topic :: System :: Archiving",
31
+ ]
32
+ dependencies = []
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=7", "ruff>=0.4", "build", "twine"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/NYLONXD/beam"
39
+ Source = "https://github.com/NYLONXD/beam"
40
+ Issues = "https://github.com/NYLONXD/beam/issues"
41
+ Changelog = "https://github.com/NYLONXD/beam/blob/main/CHANGELOG.md"
42
+
43
+ [project.scripts]
44
+ beam = "beam.cli:main"
45
+
46
+ [tool.hatch.version]
47
+ path = "src/beam/__init__.py"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/beam"]
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ include = ["src/beam", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+
58
+ [tool.ruff]
59
+ line-length = 88
60
+ target-version = "py39"
61
+
62
+ [tool.ruff.lint]
63
+ select = ["E", "F", "I", "UP", "B"]
@@ -0,0 +1,25 @@
1
+ """beam - move a project folder between two laptops over the local network.
2
+
3
+ import beam
4
+
5
+ beam.send_project("~/thesis", code="ab3f9c") # laptop A
6
+ beam.receive_project(code="ab3f9c") # laptop B
7
+ """
8
+
9
+ from ._files import DEFAULT_EXCLUDES
10
+ from ._protocol import DEFAULT_PORT, BeamError, find_sender, lan_ip
11
+ from .receiver import receive_project
12
+ from .sender import send_project
13
+
14
+ __version__ = "0.2.0"
15
+
16
+ __all__ = [
17
+ "send_project",
18
+ "receive_project",
19
+ "find_sender",
20
+ "lan_ip",
21
+ "BeamError",
22
+ "DEFAULT_EXCLUDES",
23
+ "DEFAULT_PORT",
24
+ "__version__",
25
+ ]
@@ -0,0 +1,6 @@
1
+ """Allow `python -m beam`."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,137 @@
1
+ """Walking the project tree, exclude rules, hashing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import fnmatch
6
+ import hashlib
7
+ import os
8
+ import sys
9
+ import tarfile
10
+ from pathlib import Path
11
+
12
+ from ._protocol import MANIFEST_NAME
13
+
14
+ HASH_CHUNK = 1 << 20
15
+
16
+ # Things you almost never want to carry between machines: virtualenvs are
17
+ # platform-specific, caches are regenerable, .git is better cloned.
18
+ DEFAULT_EXCLUDES = [
19
+ ".git",
20
+ ".hg",
21
+ ".svn",
22
+ "__pycache__",
23
+ "*.pyc",
24
+ "*.pyo",
25
+ ".venv",
26
+ "venv",
27
+ "env",
28
+ ".env",
29
+ ".ipynb_checkpoints",
30
+ ".mypy_cache",
31
+ ".pytest_cache",
32
+ ".ruff_cache",
33
+ ".tox",
34
+ "*.egg-info",
35
+ "node_modules",
36
+ ".DS_Store",
37
+ "Thumbs.db",
38
+ MANIFEST_NAME,
39
+ ]
40
+
41
+ # tarfile only learned about extraction filters in 3.12.
42
+ EXTRACT_KW = {"filter": "data"} if sys.version_info >= (3, 12) else {}
43
+
44
+
45
+ def human(n: float) -> str:
46
+ for unit in ("B", "KB", "MB", "GB"):
47
+ if n < 1024 or unit == "GB":
48
+ return f"{n:.0f} {unit}" if unit == "B" else f"{n:.1f} {unit}"
49
+ n /= 1024
50
+ return f"{n:.1f} GB"
51
+
52
+
53
+ def excluded(rel: Path, patterns) -> bool:
54
+ text = rel.as_posix()
55
+ for pat in patterns:
56
+ pat = pat.rstrip("/")
57
+ if fnmatch.fnmatch(rel.name, pat):
58
+ return True
59
+ if any(fnmatch.fnmatch(part, pat) for part in rel.parts):
60
+ return True
61
+ if fnmatch.fnmatch(text, pat):
62
+ return True
63
+ return False
64
+
65
+
66
+ def read_ignore_file(root: Path) -> list:
67
+ """Read .beamignore if present. Simple glob patterns, one per line."""
68
+ path = root / ".beamignore"
69
+ if not path.is_file():
70
+ return []
71
+ out = []
72
+ for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
73
+ line = line.strip()
74
+ if line and not line.startswith("#"):
75
+ out.append(line)
76
+ return out
77
+
78
+
79
+ def sha256_file(path: Path) -> str:
80
+ digest = hashlib.sha256()
81
+ with path.open("rb") as fh:
82
+ for block in iter(lambda: fh.read(HASH_CHUNK), b""):
83
+ digest.update(block)
84
+ return digest.hexdigest()
85
+
86
+
87
+ def walk(root: Path, patterns, max_bytes=None):
88
+ """Return ([(abs_path, rel_path, size)], [(rel_path, size)]) - kept, skipped."""
89
+ found, skipped = [], []
90
+ for dirpath, dirnames, filenames in os.walk(root):
91
+ here = Path(dirpath)
92
+ rel_dir = here.relative_to(root)
93
+ dirnames[:] = sorted(d for d in dirnames if not excluded(rel_dir / d, patterns))
94
+ for name in sorted(filenames):
95
+ rel = rel_dir / name
96
+ if excluded(rel, patterns):
97
+ continue
98
+ abs_path = here / name
99
+ if abs_path.is_symlink() and not abs_path.exists():
100
+ continue # broken symlink
101
+ try:
102
+ size = abs_path.stat().st_size
103
+ except OSError:
104
+ continue
105
+ if max_bytes is not None and size > max_bytes:
106
+ skipped.append((rel, size))
107
+ continue
108
+ found.append((abs_path, rel, size))
109
+ return found, skipped
110
+
111
+
112
+ def member_is_safe(name: str) -> bool:
113
+ path = Path(name)
114
+ return not path.is_absolute() and ".." not in path.parts
115
+
116
+
117
+ def safe_extract(tar: tarfile.TarFile, member: tarfile.TarInfo, dest: Path) -> None:
118
+ if not member_is_safe(member.name):
119
+ raise ValueError(f"unsafe path in stream: {member.name!r}")
120
+ if (member.islnk() or member.issym()) and not member_is_safe(member.linkname):
121
+ raise ValueError(f"unsafe link target in stream: {member.linkname!r}")
122
+ tar.extract(member, dest, **EXTRACT_KW)
123
+
124
+
125
+ class BytesReader:
126
+ """File-like wrapper so tar.addfile can consume an in-memory manifest."""
127
+
128
+ def __init__(self, data: bytes):
129
+ self.data = data
130
+ self.pos = 0
131
+
132
+ def read(self, n=-1) -> bytes:
133
+ if n is None or n < 0:
134
+ n = len(self.data) - self.pos
135
+ chunk = self.data[self.pos : self.pos + n]
136
+ self.pos += len(chunk)
137
+ return chunk