Skip to content

Commit d18aacd

Browse files
committed
stdlib/tempfile: first import
Signed-off-by: Sebastien Binet <binet@cern.ch>
1 parent fda1751 commit d18aacd

File tree

5 files changed

+417
-0
lines changed

5 files changed

+417
-0
lines changed

stdlib/stdlib.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
_ "github.com/go-python/gpython/stdlib/os"
2626
_ "github.com/go-python/gpython/stdlib/string"
2727
_ "github.com/go-python/gpython/stdlib/sys"
28+
_ "github.com/go-python/gpython/stdlib/tempfile"
2829
_ "github.com/go-python/gpython/stdlib/time"
2930
)
3031

stdlib/tempfile/tempfile.go

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// Copyright 2022 The go-python Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package tempfile provides the implementation of the python's 'tempfile' module.
6+
package tempfile
7+
8+
import (
9+
"fmt"
10+
"os"
11+
12+
"github.com/go-python/gpython/py"
13+
)
14+
15+
var (
16+
gblTempDir py.Object = py.None
17+
)
18+
19+
const tempfile_doc = `Temporary files.
20+
21+
This module provides generic, low- and high-level interfaces for
22+
creating temporary files and directories. All of the interfaces
23+
provided by this module can be used without fear of race conditions
24+
except for 'mktemp'. 'mktemp' is subject to race conditions and
25+
should not be used; it is provided for backward compatibility only.
26+
27+
The default path names are returned as str. If you supply bytes as
28+
input, all return values will be in bytes. Ex:
29+
30+
>>> tempfile.mkstemp()
31+
(4, '/tmp/tmptpu9nin8')
32+
>>> tempfile.mkdtemp(suffix=b'')
33+
b'/tmp/tmppbi8f0hy'
34+
35+
This module also provides some data items to the user:
36+
37+
TMP_MAX - maximum number of names that will be tried before
38+
giving up.
39+
tempdir - If this is set to a string before the first use of
40+
any routine from this module, it will be considered as
41+
another candidate location to store temporary files.`
42+
43+
func init() {
44+
py.RegisterModule(&py.ModuleImpl{
45+
Info: py.ModuleInfo{
46+
Name: "tempfile",
47+
Doc: tempfile_doc,
48+
},
49+
Methods: []*py.Method{
50+
py.MustNewMethod("gettempdir", gettempdir, 0, gettempdir_doc),
51+
py.MustNewMethod("gettempdirb", gettempdirb, 0, gettempdirb_doc),
52+
py.MustNewMethod("mkdtemp", mkdtemp, 0, mkdtemp_doc),
53+
py.MustNewMethod("mkstemp", mkstemp, 0, mkstemp_doc),
54+
},
55+
Globals: py.StringDict{
56+
"tempdir": gblTempDir,
57+
},
58+
})
59+
}
60+
61+
const gettempdir_doc = `Returns tempfile.tempdir as str.`
62+
63+
func gettempdir(self py.Object) (py.Object, error) {
64+
// FIXME(sbinet): lock access to glbTempDir?
65+
if gblTempDir != py.None {
66+
switch dir := gblTempDir.(type) {
67+
case py.String:
68+
return dir, nil
69+
case py.Bytes:
70+
return py.String(dir), nil
71+
default:
72+
return nil, py.ExceptionNewf(py.TypeError, "expected str, bytes or os.PathLike object, not %s", dir.Type().Name)
73+
}
74+
}
75+
return py.String(os.TempDir()), nil
76+
}
77+
78+
const gettempdirb_doc = `Returns tempfile.tempdir as bytes.`
79+
80+
func gettempdirb(self py.Object) (py.Object, error) {
81+
// FIXME(sbinet): lock access to glbTempDir?
82+
if gblTempDir != py.None {
83+
switch dir := gblTempDir.(type) {
84+
case py.String:
85+
return py.Bytes(dir), nil
86+
case py.Bytes:
87+
return dir, nil
88+
default:
89+
return nil, py.ExceptionNewf(py.TypeError, "expected str, bytes or os.PathLike object, not %s", dir.Type().Name)
90+
}
91+
}
92+
return py.Bytes(os.TempDir()), nil
93+
}
94+
95+
const mkdtemp_doc = `mkdtemp(suffix=None, prefix=None, dir=None)
96+
User-callable function to create and return a unique temporary
97+
directory. The return value is the pathname of the directory.
98+
99+
Arguments are as for mkstemp, except that the 'text' argument is
100+
not accepted.
101+
102+
The directory is readable, writable, and searchable only by the
103+
creating user.
104+
105+
Caller is responsible for deleting the directory when done with it.`
106+
107+
func mkdtemp(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
108+
var (
109+
pysuffix py.Object = py.None
110+
pyprefix py.Object = py.None
111+
pydir py.Object = py.None
112+
)
113+
err := py.ParseTupleAndKeywords(args, kwargs,
114+
"|z#z#z#:mkdtemp",
115+
[]string{"suffix", "prefix", "dir"},
116+
&pysuffix, &pyprefix, &pydir,
117+
)
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
str := func(v py.Object, typ *uint8) string {
123+
switch v := v.(type) {
124+
case py.Bytes:
125+
*typ = 2
126+
return string(v)
127+
case py.String:
128+
*typ = 1
129+
return string(v)
130+
case py.NoneType:
131+
*typ = 0
132+
return ""
133+
default:
134+
panic(fmt.Errorf("tempfile: invalid type %T (v=%+v)", v, v))
135+
}
136+
}
137+
138+
var (
139+
t1, t2, t3 uint8
140+
141+
suffix = str(pysuffix, &t1)
142+
prefix = str(pyprefix, &t2)
143+
dir = str(pydir, &t3)
144+
pattern = prefix + "*" + suffix
145+
)
146+
147+
cmp := func(t1, t2 uint8) bool {
148+
if t1 > 0 && t2 > 0 {
149+
return t1 == t2
150+
}
151+
return true
152+
}
153+
154+
if !cmp(t1, t2) || !cmp(t1, t3) || !cmp(t2, t3) {
155+
return nil, py.ExceptionNewf(py.TypeError, "Can't mix bytes and non-bytes in path components")
156+
}
157+
158+
tmp, err := os.MkdirTemp(dir, pattern)
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
typ := t1
164+
if typ == 0 {
165+
typ = t2
166+
}
167+
if typ == 0 {
168+
typ = t3
169+
}
170+
171+
switch typ {
172+
case 2:
173+
return py.Bytes(tmp), nil
174+
default:
175+
return py.String(tmp), nil
176+
}
177+
}
178+
179+
const mkstemp_doc = `mkstemp(suffix=None, prefix=None, dir=None, text=False)
180+
181+
User-callable function to create and return a unique temporary
182+
file. The return value is a pair (fd, name) where fd is the
183+
file descriptor returned by os.open, and name is the filename.
184+
185+
If 'suffix' is not None, the file name will end with that suffix,
186+
otherwise there will be no suffix.
187+
188+
If 'prefix' is not None, the file name will begin with that prefix,
189+
otherwise a default prefix is used.
190+
191+
If 'dir' is not None, the file will be created in that directory,
192+
otherwise a default directory is used.
193+
194+
If 'text' is specified and true, the file is opened in text
195+
mode. Else (the default) the file is opened in binary mode.
196+
197+
If any of 'suffix', 'prefix' and 'dir' are not None, they must be the
198+
same type. If they are bytes, the returned name will be bytes; str
199+
otherwise.
200+
201+
The file is readable and writable only by the creating user ID.
202+
If the operating system uses permission bits to indicate whether a
203+
file is executable, the file is executable by no one. The file
204+
descriptor is not inherited by children of this process.
205+
206+
Caller is responsible for deleting the file when done with it.`
207+
208+
func mkstemp(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
209+
var (
210+
pysuffix py.Object = py.None
211+
pyprefix py.Object = py.None
212+
pydir py.Object = py.None
213+
pytext py.Object = py.False // FIXME(sbinet): can we do something with that?
214+
)
215+
216+
err := py.ParseTupleAndKeywords(args, kwargs,
217+
"|z#z#z#p:mkstemp",
218+
[]string{"suffix", "prefix", "dir", "text"},
219+
&pysuffix, &pyprefix, &pydir, &pytext,
220+
)
221+
if err != nil {
222+
return nil, err
223+
}
224+
225+
str := func(v py.Object, typ *uint8) string {
226+
switch v := v.(type) {
227+
case py.Bytes:
228+
*typ = 2
229+
return string(v)
230+
case py.String:
231+
*typ = 1
232+
return string(v)
233+
case py.NoneType:
234+
*typ = 0
235+
return ""
236+
default:
237+
panic(fmt.Errorf("tempfile: invalid type %T (v=%+v)", v, v))
238+
}
239+
}
240+
241+
var (
242+
t1, t2, t3 uint8
243+
244+
suffix = str(pysuffix, &t1)
245+
prefix = str(pyprefix, &t2)
246+
dir = str(pydir, &t3)
247+
pattern = prefix + "*" + suffix
248+
)
249+
250+
cmp := func(t1, t2 uint8) bool {
251+
if t1 > 0 && t2 > 0 {
252+
return t1 == t2
253+
}
254+
return true
255+
}
256+
257+
if !cmp(t1, t2) || !cmp(t1, t3) || !cmp(t2, t3) {
258+
return nil, py.ExceptionNewf(py.TypeError, "Can't mix bytes and non-bytes in path components")
259+
}
260+
261+
f, err := os.CreateTemp(dir, pattern)
262+
if err != nil {
263+
return nil, err
264+
}
265+
266+
typ := t1
267+
if typ == 0 {
268+
typ = t2
269+
}
270+
if typ == 0 {
271+
typ = t3
272+
}
273+
274+
tuple := py.Tuple{py.Int(f.Fd())}
275+
switch typ {
276+
case 2:
277+
tuple = append(tuple, py.Bytes(f.Name()))
278+
default:
279+
tuple = append(tuple, py.String(f.Name()))
280+
}
281+
282+
return tuple, nil
283+
}

stdlib/tempfile/tempfile_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2022 The go-python Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tempfile_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/go-python/gpython/pytest"
11+
)
12+
13+
func TestTempfile(t *testing.T) {
14+
pytest.RunScript(t, "./testdata/test.py")
15+
}

0 commit comments

Comments
 (0)