Skip to content

Commit f890322

Browse files
committed
Initial commit
0 parents  commit f890322

File tree

13 files changed

+277
-0
lines changed

13 files changed

+277
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = tab
6+
indent_size = 4
7+
insert_final_newline = true
8+
end_of_line = lf
9+
10+
[*.{yml,yaml}]
11+
indent_style = space
12+
indent_size = 2

.github/.templateMarker

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
KOLANICH/python_project_boilerplate.py

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "pip"
4+
directory: "/"
5+
schedule:
6+
interval: "daily"
7+
allow:
8+
- dependency-type: "all"

.github/workflows/CI.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: CI
2+
on:
3+
push:
4+
branches: [master]
5+
pull_request:
6+
branches: [master]
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-22.04
11+
steps:
12+
- name: typical python workflow
13+
uses: KOLANICH-GHActions/typical-python-workflow@master
14+
with:
15+
github_token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
__pycache__
2+
*.py[co]
3+
/*.egg-info
4+
*.srctrlbm
5+
*.srctrldb
6+
build
7+
dist
8+
.eggs
9+
monkeytype.sqlite3
10+
/.ipynb_checkpoints

.gitlab-ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
image: registry.gitlab.com/kolanich-subgroups/docker-images/fixed_python:latest
2+
3+
variables:
4+
DOCKER_DRIVER: overlay2
5+
SAST_ANALYZER_IMAGE_TAG: latest
6+
SAST_DISABLE_DIND: "true"
7+
SAST_CONFIDENCE_LEVEL: 5
8+
CODECLIMATE_VERSION: latest
9+
10+
include:
11+
- template: SAST.gitlab-ci.yml
12+
- template: Code-Quality.gitlab-ci.yml
13+
- template: License-Management.gitlab-ci.yml
14+
15+
build:
16+
tags:
17+
- shared
18+
- linux
19+
stage: build
20+
variables:
21+
GIT_DEPTH: "1"
22+
PYTHONUSERBASE: ${CI_PROJECT_DIR}/python_user_packages
23+
24+
before_script:
25+
- export PATH="$PATH:$PYTHONUSERBASE/bin" # don't move into `variables`
26+
- apt-get update
27+
# todo:
28+
#- apt-get -y install
29+
#- pip3 install --upgrade
30+
#- python3 ./fix_python_modules_paths.py
31+
32+
script:
33+
- python3 -m build -nw bdist_wheel
34+
- mv ./dist/*.whl ./dist/RecursionSafe-0.CI-py3-none-any.whl
35+
- pip3 install --upgrade ./dist/*.whl
36+
- coverage run --source=RecursionSafe -m --branch pytest --junitxml=./rspec.xml ./tests/test.py
37+
- coverage report -m
38+
- coverage xml
39+
40+
coverage: "/^TOTAL(?:\\s+\\d+){4}\\s+(\\d+%).+/"
41+
42+
cache:
43+
paths:
44+
- $PYTHONUSERBASE
45+
46+
artifacts:
47+
paths:
48+
- dist
49+
reports:
50+
junit: ./rspec.xml
51+
cobertura: ./coverage.xml

Code_Of_Conduct.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No codes of conduct!

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include UNLICENSE
2+
include *.md
3+
include tests
4+
include .editorconfig

ReadMe.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
RecursionSafe.py [![Unlicensed work](https://raw.githubusercontent.com/unlicense/unlicense.org/master/static/favicon.png)](https://unlicense.org/)
2+
================================
3+
~~[wheel (GitLab)](https://gitlab.com/KOLANICH/RecursionSafe.py/-/jobs/artifacts/master/raw/dist/RecursionSafe-0.CI-py3-none-any.whl?job=build)~~
4+
~~[wheel (GHA via `nightly.link`)](https://nightly.link/KOLANICH-libs/RecursionSafe.py/workflows/CI/master/RecursionSafe-0.CI-py3-none-any.whl)~~
5+
~~![GitLab Build Status](https://gitlab.com/KOLANICH/RecursionSafe.py/badges/master/pipeline.svg)~~
6+
~~![GitLab Coverage](https://gitlab.com/KOLANICH/RecursionSafe.py/badges/master/coverage.svg)~~
7+
~~[![GitHub Actions](https://github.com/KOLANICH-libs/RecursionSafe.py/workflows/CI/badge.svg)](https://github.com/KOLANICH-libs/RecursionSafe.py/actions/)~~
8+
![N∅ hard dependencies](https://shields.io/badge/-N∅_Ъ_deps!-0F0)
9+
[![Libraries.io Status](https://img.shields.io/librariesio/github/KOLANICH-libs/RecursionSafe.py.svg)](https://libraries.io/github/KOLANICH-libs/RecursionSafe.py)
10+
[![Code style: antiflash](https://img.shields.io/badge/code%20style-antiflash-FFF.svg)](https://codeberg.org/KOLANICH-tools/antiflash.py)
11+
12+
A context manager to protect you from infinite recursion when walking collections.
13+
14+
Just wrap a recursive function with it. If you visit any object you are already in you will get ellipsis instead of the object, be ready for it.
15+
16+
Example
17+
-------
18+
```python
19+
recSafe = RecursionSafe()
20+
21+
objs = {
22+
"a": {},
23+
"a.b": {},
24+
"a.b.c": {},
25+
"a.b.d": {},
26+
"a.b.c.d": 14,
27+
"a.e": 10,
28+
"a.f": {},
29+
}
30+
31+
objs["a"]["b"] = objs["a.b"]
32+
objs["a.b"]["c"] = objs["a"]
33+
objs["a.b"]["d"] = objs["a.b.d"]
34+
objs["a.b.c"]["d"] = objs["a.b.c.d"]
35+
objs["a"]["e"] = objs["a.e"]
36+
objs["a"]["f"] = objs["a.f"]
37+
objs["a.f"]["g"] = objs["a"]
38+
a = objs["a"]
39+
40+
idsToNames = {id(objs[k]): k for k in objs}
41+
42+
43+
@recSafe.wrap
44+
def ownRepr(a):
45+
if isinstance(a, dict):
46+
return "{" + ", ".join((ownRepr(k) + ":" + ownRepr(v) for k, v in a.items())) + "}"
47+
else:
48+
return repr(a)
49+
50+
51+
print("repr", repr(a)) # even non-recursive objects are ellipsed
52+
print("ownRepr", ownRepr(a)) # feel the difference - non-self-referencing objects are not ellipsed
53+
```
54+

RecursionSafe.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from functools import wraps
2+
3+
4+
class RecursionSafe:
5+
__slots__ = ("visiting",)
6+
7+
class Lock:
8+
__slots__ = ("obj", "safe")
9+
10+
def __init__(self, safe, obj):
11+
self.safe = safe
12+
self.obj = obj
13+
14+
def __enter__(self):
15+
i = id(self.obj)
16+
if i not in self.safe.visiting:
17+
self.safe.visiting.add(i)
18+
return self.obj
19+
else:
20+
return ...
21+
22+
def __exit__(self, *args, **kwargs):
23+
i = id(self.obj)
24+
if i in self.safe.visiting:
25+
self.safe.visiting.remove(i)
26+
27+
def __init__(self):
28+
self.visiting = set()
29+
30+
def __call__(self, obj):
31+
return self.__class__.Lock(self, obj)
32+
33+
def wrap(self, f):
34+
@wraps(f)
35+
def wrapped(firstArg, *args, **kwargs):
36+
with self(firstArg) as firstArg:
37+
return f(firstArg, *args, **kwargs)
38+
39+
return wrapped

0 commit comments

Comments
 (0)