Skip to content

Commit 65eacd7

Browse files
authored
fix: [SUP-1872] fixed pipfile parser (#222)
1 parent 6099b74 commit 65eacd7

File tree

2 files changed

+223
-4
lines changed

2 files changed

+223
-4
lines changed

pysrc/pipfile.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,58 @@ def __init__(self, name):
1919
self.markers = None
2020
self.provenance = None # a tuple of (file name, line)
2121

22+
def __repr__(self):
23+
return str(self.__dict__())
24+
25+
def __dict__(self):
26+
return {
27+
"name": self.name,
28+
"editable": self.editable,
29+
"vcs": self.vcs,
30+
"vcs_uri": self.vcs_uri,
31+
"version": self.version,
32+
"markers": self.markers,
33+
"provenance": self.provenance,
34+
}
35+
36+
def __eq__(self, other):
37+
if isinstance(other, PipfileRequirement):
38+
return self.__dict__() == other.__dict__()
39+
return False
40+
2241
@classmethod
2342
def from_dict(cls, name, requirement_dict, pos_in_toml):
2443
req = cls(name)
2544

26-
req.version = requirement_dict.get('version')
27-
req.editable = requirement_dict.get('editable', False)
45+
req.version = parse_req(requirement_dict.get('version'))
46+
req.editable = parse_req(requirement_dict.get('editable', False))
2847
for vcs in ['git', 'hg', 'svn', 'bzr']:
2948
if vcs in requirement_dict:
3049
req.vcs = vcs
3150
req.vcs_uri = requirement_dict[vcs]
3251
break
33-
req.markers = requirement_dict.get('markers')
52+
req.markers = parse_req(requirement_dict.get('markers'))
3453
# proper file name to be injected into provenance by the calling code
3554
req.provenance = ('Pipfile', pos_in_toml[0], pos_in_toml[0])
36-
3755
return req
3856

57+
'''
58+
The toml parser returns each requirement as a tuple
59+
of the value and ending position, for multiple requirements
60+
e.g.
61+
{
62+
'version': ('*', (9, 23)),
63+
'markers': ("sys_platform == 'linux' ; python_version != '3.4'", (8, 36))
64+
} for entry waitress = {version = "*", markers="sys_platform == 'linux' ; python_version != '3.4'"}
65+
This functions returns the value without the position for one such instance
66+
e.g. parse_req(("sys_platform == 'linux' ; python_version != '3.4'", (8, 36))) returns "sys_platform == 'linux' ; python_version != '3.4'"
67+
'''
68+
def parse_req(pipfile_req):
69+
if type(pipfile_req) is tuple:
70+
return pipfile_req[0]
71+
else:
72+
return pipfile_req
73+
3974
def val_with_pos(kind, text, value, pos):
4075
return (value, pos)
4176

pysrc/test_pipfile.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# run with:
2+
# cd pysrc; python3 test_pipfile.py; cd ..
3+
4+
from pipfile import PipfileRequirement, parse
5+
from collections import namedtuple
6+
7+
import unittest
8+
9+
try:
10+
from mock import patch
11+
except:
12+
from unittest.mock import patch
13+
14+
class TestPipfileRequirement(unittest.TestCase):
15+
def test_init(self):
16+
req = PipfileRequirement("example")
17+
self.assertEqual(req.name, "example")
18+
self.assertFalse(req.editable)
19+
self.assertIsNone(req.vcs)
20+
self.assertIsNone(req.vcs_uri)
21+
self.assertIsNone(req.version)
22+
self.assertIsNone(req.markers)
23+
self.assertIsNone(req.provenance)
24+
25+
def test_from_dict(self):
26+
test_cases = [
27+
{
28+
"input": {
29+
"name": "example",
30+
"requirement_dict": {
31+
"version": '*',
32+
"editable": True,
33+
"git": 'git_uri',
34+
"markers": 'sys_platform == "linux" ; python_version != "3.4"'
35+
},
36+
"pos_in_toml": (1, 2)
37+
},
38+
"expected_output": {
39+
"name": "example",
40+
"editable": True,
41+
"vcs": "git",
42+
"vcs_uri": "git_uri",
43+
"version": "*",
44+
"markers": 'sys_platform == "linux" ; python_version != "3.4"',
45+
"provenance": ('Pipfile', 1, 1)
46+
}
47+
},
48+
{
49+
"input": {
50+
"name": "example2",
51+
"requirement_dict": {
52+
"version": ('*', (9, 23)),
53+
"editable": False,
54+
"markers": ('sys_platform == "linux" ; python_version != "3.4"', (8, 36))
55+
},
56+
"pos_in_toml": (1, 2)
57+
},
58+
"expected_output": {
59+
"name": "example2",
60+
"editable": False,
61+
"vcs": None,
62+
"vcs_uri": None,
63+
"version": "*",
64+
"markers": 'sys_platform == "linux" ; python_version != "3.4"',
65+
"provenance": ('Pipfile', 1, 1)
66+
}
67+
}
68+
]
69+
for test_case in test_cases:
70+
test_input = test_case["input"]
71+
expected_output = test_case["expected_output"]
72+
req = PipfileRequirement.from_dict(test_input["name"], test_input["requirement_dict"], test_input["pos_in_toml"])
73+
self.assertEqual(str(req), str(expected_output))
74+
75+
def test_parse(self):
76+
test_cases = [
77+
{
78+
"input": """
79+
[packages]
80+
requests = "*"
81+
flask = { version = "1.0", markers = "python_version < '3.7'" }
82+
83+
[dev-packages]
84+
pytest = "*"
85+
""",
86+
"expected_output": {
87+
"packages": [
88+
{
89+
"name": "flask",
90+
"editable": False,
91+
"vcs": None,
92+
"vcs_uri": None,
93+
"version": "1.0",
94+
"markers": "python_version < \'3.7\'",
95+
"provenance": ('Pipfile', 4, 4)
96+
},
97+
{
98+
"name": "requests",
99+
"editable": False,
100+
"vcs": None,
101+
"vcs_uri": None,
102+
"version": "*",
103+
"markers": None,
104+
"provenance": ('Pipfile', 3, 3)
105+
}
106+
],
107+
"dev-packages": [
108+
{
109+
"name": "pytest",
110+
"editable": False,
111+
"vcs": None,
112+
"vcs_uri": None,
113+
"version": "*",
114+
"markers": None,
115+
"provenance": ('Pipfile', 7, 7)
116+
}
117+
]
118+
}
119+
},
120+
{
121+
"input": """
122+
[packages]
123+
requests = {version = "==2.28.1"}
124+
125+
[requires]
126+
python_version = "3.7"
127+
""",
128+
"expected_output": {
129+
"packages": [
130+
{
131+
"name": "requests",
132+
"editable": False,
133+
"vcs": None,
134+
"vcs_uri": None,
135+
"version": "==2.28.1",
136+
"markers": None,
137+
"provenance": ('Pipfile', 3, 3)
138+
}
139+
],
140+
"dev-packages": None,
141+
}
142+
},
143+
{
144+
"input": """
145+
[[source]]
146+
url = "https://pypi.org/simple"
147+
verify_ssl = true
148+
name = "pypi"
149+
150+
[packages]
151+
"Jinja2" = "*"
152+
153+
[dev-packages]
154+
155+
[requires]
156+
""",
157+
"expected_output": {
158+
"packages": [
159+
{
160+
"name": "Jinja2",
161+
"editable": False,
162+
"vcs": None,
163+
"vcs_uri": None,
164+
"version": "*",
165+
"markers": None,
166+
"provenance": ('Pipfile', 8, 8)
167+
}
168+
],
169+
"dev-packages": [],
170+
}
171+
}
172+
]
173+
174+
for test_case in test_cases:
175+
pipfile_content = test_case["input"]
176+
expected_output = test_case["expected_output"]
177+
178+
parsed_data = parse(pipfile_content)
179+
180+
self.assertEqual(str(parsed_data), str(expected_output))
181+
182+
183+
if __name__ == '__main__':
184+
unittest.main()

0 commit comments

Comments
 (0)