Code Monkey home page Code Monkey logo

port4me's Introduction

Bash checks

PyPI version Python checks Python Code Coverage

R-CRAN check status R checks R Code Coverage

port4me - Get the Same, Personal, Free TCP Port over and over

The port4me tool:

  • finds a free TCP port in [1024,65535] that the user can open

  • is designed to work in multi-user environments

  • gives different users, different ports

  • gives the user the same port over time with high probability

  • gives different ports for different software tools

  • requires no configuration

  • can be reproduced perfectly on all operating systems and in all common programming languages

  • Available for Bash, Python, and R

Introduction

There are many tools to identify a free TCP port, where most of them return a random port. Although it works technically, it might add a fair bit of friction if a new random port number has to be entered by the user each time they need to use a specific tool.

In contrast, port4me attempts, with high probability, to provide the user with the same port each time, even when used on different days. It achieves this by scanning the same deterministic, pseudo-random sequence of ports and return the first free port detected. Each user gets their own random port sequence, lowering the risk for any two users to request the same port. The randomness is initiated with a random seed that is a function of the user's name (USER), and, optionally, the name of the software where we use the port.

The port4me algorithm can be implemented in most known programming languages, producing perfectly reproducable sequencing regardless of implementation language.

A quick introduction

Assuming we're logged in as user alice in a Bash shell, calling port4me without arguments gives us a free port:

{alice}$ port4me
30845

As we will see later, each user on the system is likely to get their own unique port. Because of this, it can be used to specifying a port that some software tool should use, e.g.

{alice}$ jupyter notebook --port "$(port4me)"

As long as this port is available, alice will always get the same port across shell sessions and over time. For example, if they return next week and retry, it's likely they still get:

{alice}$ port4me
30845
{alice}$ port4me
30845

However, if port 30845 is already occupied, the next port in the pseudo-random sequence is considered, e.g.

{alice}$ port4me
19654

To see the first five ports scanned, run:

{alice}$ port4me --list=5
30845
19654
32310
63992
15273

User-specific, deterministic, pseudo-random port sequence

This random sequence is initiated by a random seed that can be set via the hashcode of a seed string. By default, it is based on the name of the current user (e.g. environment variable $USER). For example, when user bob uses the port4me tool, they see another set of ports being scanned:

{bob}$ port4me --list=5
54242
4930
42139
14723
55707

For testing and demonstration purposes, one can emulate another user by specifying option --user, e.g.

{alice}$ port4me
30845
{alice}$ port4me --user=bob
54242
{alice}$ port4me --user=carol
34307

Different ports for different software tools

Sometimes a user would like to use two, or more, ports at the same time, e.g. one port for RStudio Server and another for Jupyter Notebook. In such case, they can specify option --tool, which results in a port sequence that is unique to both the user and the tool. For example,

{alice}$ port4me
30845
{alice}$ port4me --tool=rstudio
22486
{alice}$ port4me --tool=jupyter-notebook
29525

For conveniency, if the first option is unnamed, then it is assumed it specifies the --tool option. This means we can use the following sort form as well:

{alice}$ port4me jupyter-notebook
47467

This allows us to get different ports for different software tools, e.g.

{alice}$ rserver --www-port "$(port4me rstudio)"

and

{alice}$ jupyter notebook --port "$(port4me jupyter-notebook)"

Avoid using ports commonly used elsewhere

Since there is a limited set of ports available (1024-65535), there is always a risk that another process occupies any given port. The more users there are on the same machine, the higher the risk is for this to happen. If a user is unlucky, they might experience this frequently. For example, alice might find that the first port (30845) works only one out 10 times, the second port (19654) works 99 out 100 times, and the third one (32310) works rarely. If so, they might choose to exclude the ports that are most likely to be occupied by specifying them as a comma-separated values via option --exclude, e.g.

{alice}$ port4me --exclude=30845,32310
19654

An alternative to specify them via a command-line option, is to specify them via environment variable PORT4ME_EXCLUDE, e.g.

{alice}$ PORT4ME_EXCLUDE=30845,32310 port4me
19654

To set this permanently, append:

## port4me customization
## https://github.com/HenrikBengtsson/port4me
PORT4ME_EXCLUDE=30845,32310
export PORT4ME_EXCLUDE

to the shell startup script, e.g. ~/.bashrc.

This increases the chances for the user to end up with the same port over time, which is convenient, because then they can reuse the same call, which is available in the command-line history, each time without having to change the port parameter.

The environment variable PORT4ME_EXCLUDE is intended to be used by the individual user. To specify a set of ports to be excluded regardless of user, set PORT4ME_EXCLUDE_SITE. For example, the systems administrator, can choose to exclude an additional set of ports by adding the following to file /etc/profile.d/port4me.sh:

## port4me: always exclude commonly used ports
## https://github.com/HenrikBengtsson/port4me

PORT4ME_EXCLUDE_SITE=

## MySQL
PORT4ME_EXCLUDE_SITE=$PORT4ME_EXCLUDE_SITE,3306

## ZeroMQ
PORT4ME_EXCLUDE_SITE=$PORT4ME_EXCLUDE_SITE,5670

## Redis
PORT4ME_EXCLUDE_SITE=$PORT4ME_EXCLUDE_SITE,6379

## Jupyter
PORT4ME_EXCLUDE_SITE=$PORT4ME_EXCLUDE_SITE,8888

export PORT4ME_EXCLUDE_SITE

In addition to ports excluded via above mechanisms, port4me excludes ports that are considered unsafe by the Chrome and Firefox web browsers. This behavior can be controlled by environment variable PORT4ME_EXCLUDE_UNSAFE, which defaults to {chrome},{firefox}. Token {chrome} expands to the value of PORT4ME_EXCLUDE_UNSAFE_CHROME, which defaults to the set of ports that Chrome blocks, and {firefox} expands to to the value of PORT4ME_EXCLUDE_UNSAFE_FIREFOX, which defaults to the set of ports that Firefox blocks.

Analogously to excluding a set of ports, one can limit the set of ports to be scanned by specifying command-line option --include, e.g.

{alice}$ port4me --include=2000-2123,4321,10000-10999
10451

where the default corresponds to --include=1024-65535. Analogously to --exclude, --include can be specified via environment variables PORT4ME_INCLUDE and PORT4ME_INCLUDE_SITE.

Scan a predefined set of ports before pseudo-random ones

In addition to scanning the user-specific, pseudo-random port sequence for a free port, it is possible to also consider a predefined set of ports prior to the random ones by specifying command-line option --prepend, e.g.

{alice}$ port4me --prepend=4321,11001 --list=5
4321
11001
30845
19654
32310

An alternative to specify them via a command-line option, is to specify them via environment variable PORT4ME_PREPEND, e.g.

{alice}$ PORT4ME_PREPEND=4321,11001 port4me --list=5
4321
11001
30845
19654
32310

The environment variable PORT4ME_PREPEND is intended to be used by the individual user. To specify a set of ports to be prepended regardless of user, set PORT4ME_PREPEND_SITE.

Tips and Tricks

All port4me implementations output the identified port to standard output (stdout). This makes it easy to capture by standard shell methods, e.g. port="$(port4me)". If you'd like to see which port number was generated, use tee to send the port also to the standard error (stderr), which can be seen in the terminal. For example,

{alice}$ jupyter notebook --port "$(port4me --tool=jupyter-notebook | tee /dev/stderr)"
29525

Installation

Bash, command-line tool

To install the Bash version of portme, do:

VERSION=0.7.1
curl -L -O https://github.com/HenrikBengtsson/port4me/archive/refs/tags/"${VERSION}.tar.gz"
tar -x -f "${VERSION}.tar.gz"
export PREFIX=/path/to/port4me/   ## must be an absolute path to a folder
(cd "port4me-${VERSION}/bash"; make install)

Then run it as:

$ export PATH=/path/to/port4me/bin:$PATH
$ port4me --version
0.7.1

R package

To install the R portme package, which is available on CRAN, call the following from within R:

install.packages("port4me")

To try it out, call:

> port4me::port4me("jupyter-notebook")
[1] 47467

or

$ Rscript -e port4me::port4me jupyter-notebook
29525

Python package

The Python package port4me is available PyPI. To install the Python portme package to your personal Python package library, call the following from the command line:

$ pip install --user port4me

To install it to a Python virtual environment, drop option --user.

To try it out, call:

>>> from port4me import port4me
>>> port4me("jupyter-notebook")
29525

or

$ python -m port4me --tool=jupyter-notebook
29525

The port4me Algorithm

Requirements

  • It should be possible to implement the algorithm using 32-bit unsigned integer arithmetic. One must not assume that the largest represented integer can exceed $2^{32} - 1$.

  • The pseudo-randomized port sequence should sample ports uniformly over $[1024, 65535]$.

  • At a minimum, it should be possible to implement the algorithm in vanilla Sh*, Csh, Bash, C, C++, Fortran, Lua, Python, R, and Ruby, with no need for add-on packages beyond what is available from their core distribution. (*) Shells that do not support integer arithmetic may use tools such as expr, dc, bc, and awk for these calculations.

  • All programming languages should produce the exact same pseudo-random port sequences given the same random seed.

  • The implementations should be written such that they work also when sourced, or copy'and'pasted into source code elsewhere, e.g. in R and Python scripts.

  • The identified, free port should be outputted to the standard output (stdout) as digits only, without any prefix or suffix symbols.

  • The user should be able to exclude a pre-defined set of ports by specifying environment variable PORT4ME_EXCLUDE, e.g. PORT4ME_EXCLUDE=8080,4321.

  • The system administrator should be able to specify a pre-defined set of ports to be excluded by specifying environment variable PORT4ME_EXCLUDE_SITE, e.g. PORT4ME_EXCLUDE_SITE=8080,4321. This works complementary to PORT4ME_EXCLUDE.

  • The user should be able to skip a certain number of random ports at their will by specifying environment variable PORT4ME_SKIP, e.g. PORT4ME_SKIP=5. The default is to not skip, which corresponds to PORT4ME_SKIP=0. Skipping should apply after ports are excluding by PORT4ME_EXCLUDE and PORT4ME_EXCLUDE_SITE.

  • New implementations should perfectly reproduce the port sequences produced by already existing implementations.

Design

  • A Linear congruential generator (LCG) will be used to generate the pseudo-random port sequence

    • the next seed, $s_{n+1}$ is calculated based on the current seed $s_n$ and parameters $a, c, m > 1$ as $s_{n+1} = (a * s_{n} + c) % m$

    • the LCG algorithm must not assume that the current LCG seed is within $[0,m-1]$, i.e. it should apply modulo $m$ on the seed first to avoid integer overflow

    • the LCG algorithm may produce the same output seed as input seed, which may happen when the seed is $s_n = m - (a - c)$. To avoid this resulting in a constant LCG stream, increment the seed by one and recalculate whenever this happens

    • LCG parameters should be $m = 2^{16} + 1$, $a = 75$, and $c = 74$ ("ZX81")

      • this requires only 32-bit integer arithmetic, because $m < 2^{32}$

      • if the initial seed is $s_0 = m - (a - c)$, which here is $m - 1 = 2^{16}$, then the next LCG seed will be the same, which is then handled by the above increment-by-one workaround

  • A 32-bit integer string hashcode will be used to generate an integer in $[0,2^{32}-1]$ from an ASCII string with any number of characters. The hashcode algorithm is based on the Java hashcode algorithm, but uses unsigned 32-bit integers in $[0,2^{32}-1]$, instead of signed ones in $[-2^{31},2^{31}-1]$

  • The string hashcode is used as the initial LCG seed:

    • the LCG seed should be in $[0,m-1]$

    • given hashcode $h$, we can generate the initial LCG seed as $h$ modulo $m$

port4me's People

Contributors

barracuda156 avatar finnventor avatar henrikbengtsson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

port4me's Issues

Python: Add NEWS.md file

Add a python/NEWS.md file, cf. ditto for Bash and R.

@Finnventor , do you know if Python, or the Python community, uses another file name than NEWS.md for their packages' change log?

Add option to split/tee to stderr, or another destination

Make it possible to split/tee the port to standard error, e.g.

{alice}$ port=$(port4me --split=stderr)
30845

In R,

> port <- port4me::port4me(split = "stderr")
30845
> str(port)
 int 30845

and

$ Rscript -e "shiny::runApp(port = port4me::port4me(split = 'stderr'))"
30845

Should be controllable via an environment variable, e.g.

PORT4ME_SPLIT="stderr" Rscript -e "shiny::runApp(port = port4me::port4me())"
30845

Bash: port4me thinks an occupied port is available

# R
$ Rscript -e port4me::port4me --test=37216 && echo "avail" || echo "bound"
bound

# Python
$ python3 -m port4me --test=37216 && echo "avail" || echo "bound"
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__main__.py", line 25, in <module>
    if port4me(**args):
       ^^^^^^^^^^^^^^^
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__init__.py", line 167, in port4me
    return is_port_free(test)
           ^^^^^^^^^^^^^^^^^^
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__init__.py", line 33, in is_port_free
    s.bind(("", port))
OSError: [Errno 98] Address already in use
bound

# Bash
$ port4me --test=37216 && echo "avail" || echo "bound"
avail

$ netstat | grep 37216
tcp        0      0 dev1.wynton.ucsf.:37216 bmd5.wynton.ucsf.e:8105 ESTABLISHED

$ ss | grep 37216
tcp   ESTAB      0      0                                                               172.26.44.131:37216          172.26.36.39:8105

REFERENCES: IANA's rfc6335

Section 6 ('Port Number Ranges') of rfc6335 ('Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry') mentions three categories of TCP port numbers:

  • the System Ports, also known as the Well Known Ports, from 0-1023 (assigned by IANA)

  • the User Ports, also known as the Registered Ports, from 1024-49151 (assigned by IANA)

  • the Dynamic Ports, also known as the Private or Ephemeral Ports, from 49152-65535 (never assigned)

These are further clarified in Section 8.1.2 ('Variances for Specific Port Number Ranges'):

  • Ports in the Dynamic Ports range (49152-65535) have been
    specifically set aside for local and dynamic use and cannot be
    assigned through IANA. Application software may simply use any
    dynamic port that is available on the local host, without any sort
    of assignment. On the other hand, application software MUST NOT
    assume that a specific port number in the Dynamic Ports range will
    always be available for communication at all times, and a port
    number in that range hence MUST NOT be used as a service
    identifier.

  • Ports in the User Ports range (1024-49151) are available for
    assignment through IANA, and MAY be used as service identifiers
    upon successful assignment. Because assigning a port number for a
    specific application consumes a fraction of the shared resource
    that is the port number registry, IANA will require the requester
    to document the intended use of the port number. For most IETF
    protocols, ports in the User Ports range will be assigned under
    the "IETF Review" or "IESG Approval" procedures [RFC5226] and no
    further documentation is required. Where these procedures do not
    apply, then the requester must input the documentation to the
    "Expert Review" procedure [RFC5226], by which IANA will have a
    technical expert review the request to determine whether to grant
    the assignment. Regardless of the path ("IETF Review", "IESG
    Approval", or "Expert Review"), the submitted documentation is
    expected to be the same, as described in this section, and MUST
    explain why using a port number in the Dynamic Ports range is
    unsuitable for the given application. Further, IANA MAY utilize
    the "Expert Review" process informally to inform their position in
    participating in "IETF Review" and "IESG Approval".

  • Ports in the System Ports range (0-1023) are also available for
    assignment through IANA. Because the System Ports range is both
    the smallest and the most densely assigned, the requirements for
    new assignments are more strict than those for the User Ports
    range, and will only be granted under the "IETF Review" or "IESG
    Approval" procedures [RFC5226]. A request for a System Port
    number MUST document both why using a port number from the
    Dynamic Ports range is unsuitable and why using a port number
    from the User Ports range is unsuitable for that application.

Python: Does `port4me(test=PORT)` work?

Shouldn't the following:

>>> from port4me import port4me
>>> port4me(test = 80)
True

return False, because port 80 is not available, cf.

> port4me::port4me(test = 80)
[1] FALSE

and

$ port4me --test=80 && echo "true" || echo "false"
false

Python: Add package version

Add package version, so it's possible to infer the version of the port4me Python package. I don't know if there is a standard way to do this in Python.

NOTE: How to call `port4me` executable from Python and capture result

Until we have a Python implementation available (#17), one can call the Bash implementation as:

import os
port = int(os.popen("port4me --tool=jupyter-notebook").read())

Example:

>>> import os
>>> port = int(os.popen("port4me --tool=jupyter-notebook").read())
>>> port
35386
>>> type(port)
<class 'int'>

Python: `python -m port4me --test=PORT` to give answer via exit code - not stdout

We have that port4me --test=PORT doesn't output anything but it exits with exit code 0 if the port is available, otherwise with exit code 1. For example,

$ port4me --test=80
henrik@hb-x1-2023:~/repositories/port4me/python$ echo $?
1

This is reflected in the new R CLI interface;

$ Rscript -e port4me::port4me --test=80
$ echo $?
1

I think Python should behave the same, i.e.

$ python -m port4me --test=80
$ echo $?
1

TESTS: Fake availability

Some of the tests assume that there are no busy ports. When the intended port is already bound by another process on the system, the test fails.

Fix these tests by faking that all ports are available.

LICENSE

Add LICENSE file to Bash.
Make sure they all follow the MIT format.

R: The port4me CLI interface requires `--args` for R (< 4.2.0)

With R (< 4.2.0), we need to add R-specific --args in order for the following arguments to reach port4me instead of Rscript, i.e.

$ Rscript -e port4me::port4me --args --version
0.6.0.9002

If we don't use this, we get:

$ Rscript -e port4me::port4me --version
R version 4.1.0 (2021-05-18) -- "Camp Pontanezen"
Copyright (C) 2021 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https://www.gnu.org/licenses/.

A base-R illustration of what's going on

$ Rscript -e 42 --version
R version 4.1.0 (2021-05-18) -- "Camp Pontanezen"
Copyright (C) 2021 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)
...
$ Rscript -e 42 --args --version
[1] 42

The proper way to do this with R is actually to always use --args, but in R (>= 4.2.0) [2022-04-22], it works without too, e.g.

$ Rscript -e 42 --version
[1] 42

Bias: Ports 1024-2047 are sampled twice as frequent as the other ports in [1024,65535]

Ports 1024-2047 are sampled twice as frequent as the other ports in [1024,65535]. This could be related to LCG generates numbers in [0,65535], which we then bring in to [1024,65535] using the modulo operator. It's almost as if values 0-1023 are folded onto [1024,65535]. Needs investigation to understand and fix.

Reproducible example:

> ports <- port4me(list = 1e6, user = "alice")
> length(ports)
[1] 1000000

> t <- table(ports)
> table(t)
t
   15    16    30    31    32 
47048 16440   566   396    62

> summary(t >= 30)
   Mode   FALSE    TRUE 
logical   63488    1024 

> 1024/65535
[1] 0.01562524

> range(as.integer(names(t[t >= 30])))
[1] 1024 2047

> t[t >= 30]
ports
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 
  30   30   30   31   31   31   31   30   30   31   31   30   31   31   30   30 
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 
  31   30   30   30   31   31   31   32   30   30   30   30   30   30   31   30 
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 
  31   30   30   31   30   31   30   30   30   30   30   30   31   30   30   30 
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 
  30   31   30   30   31   31   30   30   30   31   30   30   30   30   30   30 
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 
  30   30   30   30   31   31   31   30   31   32   30   30   30   31   30   30 
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 
  31   30   30   30   30   30   32   30   30   30   31   30   30   30   31   30 
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 
  32   31   30   30   31   31   30   30   30   30   30   30   30   30   31   31 
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 
  30   30   30   30   32   31   31   31   30   31   31   30   31   30   31   31 
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 
  30   31   32   30   31   31   30   30   31   30   30   32   31   30   30   30 
1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 
  30   30   30   30   31   30   30   31   32   31   30   30   30   30   31   32 
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 
  30   30   30   30   30   32   31   30   31   31   30   31   30   30   31   30 
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 
  30   31   30   31   31   31   31   30   30   31   31   31   31   31   31   32 
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 
  30   30   30   31   30   31   31   32   30   30   30   30   30   31   31   30 
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 
  31   31   30   31   31   30   31   30   30   30   30   31   30   31   30   30 
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 
  30   31   30   31   30   32   31   30   30   30   31   31   31   30   30   31 
1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 
  31   30   30   30   30   30   30   30   31   32   30   31   30   31   30   30 
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 
  31   30   30   30   31   31   30   30   31   30   30   30   30   31   30   30 
1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 
  31   31   31   31   30   32   32   31   31   30   30   30   31   30   30   30 
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 
  30   30   31   31   30   30   31   30   32   32   30   31   30   31   30   30 
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 
  31   30   31   30   30   31   30   30   30   31   30   30   31   30   31   30 
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 
  30   30   30   31   30   31   30   30   31   31   30   30   31   30   31   30 
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 
  31   31   30   30   31   31   31   30   30   31   31   30   31   30   31   30 
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 
  30   30   30   30   30   30   30   31   30   30   30   31   32   30   31   30 
1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 
  30   30   30   30   31   30   31   32   30   31   30   31   30   31   30   31 
1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 
  30   30   30   31   31   30   31   31   30   30   31   30   30   31   30   30 
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 
  30   30   30   32   31   30   31   31   30   30   30   30   30   31   30   31 
1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 
  30   31   31   31   31   31   30   31   31   30   30   32   31   30   30   30 
1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 
  31   30   31   31   30   31   30   30   31   31   30   30   30   30   31   32 
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 
  31   32   30   30   30   32   32   30   30   30   30   31   30   30   32   30 
1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 
  30   30   31   31   32   31   31   30   30   30   31   32   30   30   30   30 
1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 
  30   30   30   31   30   31   30   30   31   30   32   30   30   31   31   30 
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 
  31   30   31   31   30   31   30   30   30   30   30   30   31   31   31   30 
1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 
  30   31   30   30   31   30   30   31   31   31   30   31   30   30   31   30 
1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 
  31   30   30   31   30   30   31   31   30   30   31   30   30   30   31   31 
1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 
  32   31   30   30   30   30   31   31   31   30   30   30   30   30   32   30 
1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 
  30   30   30   32   31   32   30   30   32   30   30   31   32   31   30   31 
1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 
  30   30   30   32   30   31   30   30   31   30   31   32   30   30   31   30 
1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 
  30   31   32   30   30   31   30   31   31   31   30   31   31   31   31   30 
1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 
  30   30   30   31   32   31   31   32   30   30   30   30   30   30   31   31 
1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 
  30   31   31   30   30   30   31   31   31   30   31   30   30   30   32   31 
1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 
  31   31   31   32   30   31   30   30   30   30   30   30   31   31   30   32 
1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 
  31   30   32   30   30   31   30   30   30   31   31   30   31   31   30   31 
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 
  31   30   31   30   31   30   30   31   31   30   30   30   31   30   30   31 
1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 
  32   31   31   31   31   32   30   30   30   30   30   31   30   31   32   31 
1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 
  32   30   30   30   31   30   30   31   31   30   31   31   31   30   32   30 
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 
  32   31   30   31   31   30   30   30   31   30   31   30   31   31   31   31 
1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 
  30   30   30   31   31   31   31   30   31   30   30   30   30   30   31   30 
1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 
  31   30   30   30   30   30   31   30   30   31   30   31   31   30   31   31 
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 
  32   31   31   31   30   31   30   31   30   31   31   32   30   31   31   30 
1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 
  30   30   31   30   30   31   31   30   30   30   30   30   30   31   30   30 
1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 
  31   31   31   31   31   31   30   30   31   30   31   31   30   31   30   30 
1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 
  32   31   30   31   30   31   31   31   30   31   30   30   31   30   30   30 
1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 
  30   31   30   31   31   30   30   31   31   31   31   30   30   30   31   31 
1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 
  30   31   31   30   30   30   30   30   31   30   30   31   30   31   32   30 
1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 
  30   30   30   32   30   30   30   30   30   31   31   30   30   30   31   31 
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 
  30   31   32   30   30   31   30   30   30   31   30   30   30   30   31   30 
1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 
  30   31   30   31   31   30   31   31   30   30   30   30   31   30   31   30 
1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 
  31   30   30   30   31   30   31   31   30   31   30   31   32   31   30   30 
1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 
  30   31   30   32   30   30   31   31   30   30   30   30   31   31   30   31 
1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 
  31   32   30   30   32   30   30   30   30   30   31   31   30   30   31   30 
1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 
  31   31   31   31   31   31   31   31   31   31   30   30   31   31   31   30 
2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 
  30   30   31   30   30   30   30   31   31   31   30   31   31   30   31   31 
2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 
  31   30   30   31   30   31   31   30   30   30   31   30   31   30   31   31 
2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 
  30   31   30   31   32   31   30   30   31   31   30   31   30   30   31   30 

README: Incorrect example output

The example:

{alice}$ port4me --exclude=30845,32310
20678

seems incorrect, because:

$ PORT4ME_USER=alice port4me --list=5
30845
19654
32310
63992
15273

So, the example needs to be updated to:

{alice}$ port4me --exclude=30845,32310
19654

The R vignette needs to be corrected the same way.

TEST: Stress test with all possible integer seeds in [0,MAX_UINT-1]

Test port4me with all possible integer seeds, and list the first 100 random ports for each seed. At a minimum, it should always produce valid port numbers.

Possible problems:

  • the LCG ends up being stuck in a single state (there's an internal sanity check for this)
  • the LCG seed becomes invalid, e.g. zero (this is actually ok)

Python: CLI --list=N should replicate the Bash output

For example, Bash gives:

$ port4me --list=5
28472
38290
53733

and now also R:

$ Rscript -e port4me::port4me --list=3
28472
38290
53733

However, Python gives:

$ python -m port4me --list=3
[28472, 38290, 53733]

I think the latter should format the output the same, i.e.

$ python -m port4me --list=3
28472
38290
53733

This makes the CLI output easy to parse, etc.

Python: "ValueError: Missing `tool.hatch.version` configuration" when trying to install

@Finnventor, I get ValueError: Missing tool.hatch.version configuration when I try to install the latest Python version. Do you know how to resolve this? (I'm puzzled, because it does not occur on GitHub Actions, which basically does the same commands).

$ pip install .
Defaulting to user installation because normal site-packages is not writeable
Processing /home/henrik/repositories/port4me/python
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [20 lines of output]
      Traceback (most recent call last):
        File "/home/henrik/.local/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/home/henrik/.local/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "/home/henrik/.local/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 152, in prepare_metadata_for_build_wheel
          whl_basename = backend.build_wheel(metadata_directory, config_settings)
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/build.py", line 56, in build_wheel
          return os.path.basename(next(builder.build(wheel_directory, ['standard'])))
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/builders/plugin/interface.py", line 93, in build
          self.metadata.validate_fields()
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/metadata/core.py", line 243, in validate_fields
          _ = self.version
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/metadata/core.py", line 128, in version
          self._version = self._get_version()
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/metadata/core.py", line 226, in _get_version
          version = self.hatch.version.cached
        File "/tmp/henrik/pip-build-env-eopumzls/overlay/local/lib/python3.10/dist-packages/hatchling/metadata/core.py", line 1384, in version
          raise ValueError(message)
      ValueError: Missing `tool.hatch.version` configuration
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

Session information

$ python --version
Python 3.10.6

$ hatch --version
Hatch, version 1.7.0

$ python -m pip install --upgrade pip
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: pip in /home/henrik/.local/lib/python3.10/site-packages (23.1.2)

$ pip install flake8 pytest
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: flake8 in /home/henrik/.local/lib/python3.10/site-packages (6.0.0)
Requirement already satisfied: pytest in /home/henrik/.local/lib/python3.10/site-packages (7.3.2)
Requirement already satisfied: mccabe<0.8.0,>=0.7.0 in /home/henrik/.local/lib/python3.10/site-packages (from flake8) (0.7.0)
Requirement already satisfied: pycodestyle<2.11.0,>=2.10.0 in /home/henrik/.local/lib/python3.10/site-packages (from flake8) (2.10.0)
Requirement already satisfied: pyflakes<3.1.0,>=3.0.0 in /home/henrik/.local/lib/python3.10/site-packages (from flake8) (3.0.1)
Requirement already satisfied: iniconfig in /home/henrik/.local/lib/python3.10/site-packages (from pytest) (2.0.0)
Requirement already satisfied: packaging in /usr/lib/python3/dist-packages (from pytest) (21.3)
Requirement already satisfied: pluggy<2.0,>=0.12 in /home/henrik/.local/lib/python3.10/site-packages (from pytest) (1.0.0)
Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /home/henrik/.local/lib/python3.10/site-packages (from pytest) (1.1.1)
Requirement already satisfied: tomli>=1.0.0 in /home/henrik/.local/lib/python3.10/site-packages (from pytest) (2.0.1)

R: Add CLI interface

Add CLI interface for R, e.g.

$ Rscript -e port4me::port4me
30845

$ Rscript -e port4me::port4me --version
0.5.1-9003

Python: `python -m port4me jupyter-notebook` should also work

Right now we get:

$ python -m port4me jupyter-notebook
usage: python -m port4me [-h] [--tool TOOL] [--user USER] [--prepend PORTS] [--include PORTS] [--exclude PORTS] [--list N]
                         [--test PORT] [--version]
python -m port4me: error: unrecognized arguments: jupyter-notebook

but I think it should default to:

$ python -m port4me --tool=jupyter-notebook
38074

That is, it should work like:

$ python -m port4me jupyter-notebook
38074

just like how it works for Bash and the R CLI, e.g.

$ port4me jupyter-notebook
38074

henrik@hb-x1-2023:~/repositories/port4me/python$ Rscript -e port4me::port4me jupyter-notebook
38074

Rename 'include' to something else, e.g. 'always'?

Maybe we want to reserve 'include' for defining the range of ports to consider, i.e. the default is --include=1024-65535. This is the refined with --exclude=<ports>.

If so, we need to rename the current use to something else, e.g. --always=<ports>.

Python: Is CLI '--foo bar' the preferred standard in Python instead of '--foo=bar'?

Is CLI --foo bar the preferred standard in Python instead of --foo=bar? I see it used somewhere, but I'm not sure. I definitely like the --foo=bar format, because it makes it non-amigous. For instance, is:

python -m somepkg --foo bar

calling with boolean flag --foo and passing bar as an argument (e.g. a filename), or is the same as:

python -m somepkg --foo=bar

By sticking to only --foo=bar this 100% clear.

If we use --foo=bar, we should update the help to use that too:

$ python -m port4me --help
usage: python -m port4me [-h] [--tool TOOL] [--user USER] [--prepend PORTS] [--include PORTS] [--exclude PORTS] [--list N]
                         [--test PORT]

port4me: Get the Same, Personal, Free TCP Port over and over

options:
  -h, --help       show this help message and exit
  --tool TOOL      Used in the seed when generating port numbers, to get a different port sequence for different tools.
  --user USER      Used in the seed when generating port numbers. Defaults to determining the username with getuser().
  --prepend PORTS  A list of ports to try first
  --include PORTS  If specified, skip any ports not in this list
  --exclude PORTS  Skip any ports in this list
  --list N         Instead of returning a single port, return a list of this many ports without checking if they are free.
  --test PORT      If specified, return whether the port `PORT` is not in use. All other parameters will be ignored.

Python: CLI call should not output error; just return silently with an exit

$ python3 -m port4me --test=37216; echo $?
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__main__.py", line 25, in <module>
    if port4me(**args):
       ^^^^^^^^^^^^^^^
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__init__.py", line 167, in port4me
    return is_port_free(test)
           ^^^^^^^^^^^^^^^^^^
  File "/wynton/home/cbi/hb/.local/lib/python3.11/site-packages/port4me/__init__.py", line 33, in is_port_free
    s.bind(("", port))
OSError: [Errno 98] Address already in use
1
$ 

The traceback and the error message should not be shown here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.