##// END OF EJS Templates
switch to ruff from black
super-admin -
r1170:e15a1319 default
parent child Browse files
Show More
@@ -1,510 +1,517 b''
1 1 """
2 2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
3 3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
4 4 """
5 5
6 import contextlib
7 import dataclasses
6 8 import gc
7 import os
8 import sys
9 9 import math
10 import time
11 import threading
12 import traceback
10 import os
13 11 import random
14 12 import socket
15 import dataclasses
13 import sys
14 import threading
15 import time
16 import traceback
17
16 18 from gunicorn.glogging import Logger
17 19
18 20
19 21 def get_workers():
20 22 import multiprocessing
23
21 24 return multiprocessing.cpu_count() * 2 + 1
22 25
23 26
24 27 bind = "127.0.0.1:10010"
25 28
26 29
27 30 # Error logging output for gunicorn (-) is stdout
28 errorlog = '-'
31 errorlog = "-"
29 32
30 33 # Access logging output for gunicorn (-) is stdout
31 accesslog = '-'
34 accesslog = "-"
32 35
33 36
34 37 # SERVER MECHANICS
35 38 # None == system temp dir
36 39 # worker_tmp_dir is recommended to be set to some tmpfs
37 40 worker_tmp_dir = None
38 41 tmp_upload_dir = None
39 42
40 43 # use re-use port logic
41 44 #reuse_port = True
42 45
43 46 # Custom log format
44 47 #access_log_format = (
45 48 # '%(t)s %(p)s INFO [GNCRN] %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
46 49
47 50 # loki format for easier parsing in grafana
48 access_log_format = (
49 'time="%(t)s" pid=%(p)s level="INFO" type="[GNCRN]" ip="%(h)-15s" rqt="%(L)s" response_code="%(s)s" response_bytes="%(b)-6s" uri="%(m)s:%(U)s %(q)s" user=":%(u)s" user_agent="%(a)s"')
51 access_log_format = 'time="%(t)s" pid=%(p)s level="INFO" type="[GNCRN]" ip="%(h)-15s" rqt="%(L)s" response_code="%(s)s" response_bytes="%(b)-6s" uri="%(m)s:%(U)s %(q)s" user=":%(u)s" user_agent="%(a)s"'
50 52
51 53
52 54 # Sets the number of process workers. More workers means more concurrent connections
53 55 # RhodeCode can handle at the same time. Each additional worker also it increases
54 56 # memory usage as each has it's own set of caches.
55 57 # Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
56 58 # than 8-10 unless for huge deployments .e.g 700-1000 users.
57 59 # `instance_id = *` must be set in the [app:main] section below (which is the default)
58 60 # when using more than 1 worker.
59 61 workers = 6
60 62
61 63 # self adjust workers based on CPU count, to use maximum of CPU and not overquota the resources
62 64 # workers = get_workers()
63 65
64 66 # Gunicorn access log level
65 loglevel = 'info'
67 loglevel = "info"
66 68
67 69 # Process name visible in a process list
68 proc_name = 'rhodecode_vcsserver'
70 proc_name = "rhodecode_vcsserver"
69 71
70 72 # Type of worker class, one of `sync`, `gevent`
71 73 # currently `sync` is the only option allowed.
72 worker_class = 'sync'
74 worker_class = "sync"
73 75
74 76 # The maximum number of simultaneous clients. Valid only for gevent
75 77 worker_connections = 10
76 78
77 79 # Max number of requests that worker will handle before being gracefully restarted.
78 80 # Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
79 81 max_requests = 2000
80 82 max_requests_jitter = int(max_requests * 0.2) # 20% of max_requests
81 83
82 84 # The maximum number of pending connections.
83 85 # Exceeding this number results in the client getting an error when attempting to connect.
84 86 backlog = 64
85 87
86 88 # The Amount of time a worker can spend with handling a request before it
87 89 # gets killed and restarted. By default, set to 21600 (6hrs)
88 90 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
89 91 timeout = 21600
90 92
91 93 # The maximum size of HTTP request line in bytes.
92 94 # 0 for unlimited
93 95 limit_request_line = 0
94 96
95 97 # Limit the number of HTTP headers fields in a request.
96 98 # By default this value is 100 and can't be larger than 32768.
97 99 limit_request_fields = 32768
98 100
99 101 # Limit the allowed size of an HTTP request header field.
100 102 # Value is a positive number or 0.
101 103 # Setting it to 0 will allow unlimited header field sizes.
102 104 limit_request_field_size = 0
103 105
104 106 # Timeout for graceful workers restart.
105 107 # After receiving a restart signal, workers have this much time to finish
106 108 # serving requests. Workers still alive after the timeout (starting from the
107 109 # receipt of the restart signal) are force killed.
108 110 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
109 111 graceful_timeout = 21600
110 112
111 113 # The number of seconds to wait for requests on a Keep-Alive connection.
112 114 # Generally set in the 1-5 seconds range.
113 115 keepalive = 2
114 116
115 117 # Maximum memory usage that each worker can use before it will receive a
116 118 # graceful restart signal 0 = memory monitoring is disabled
117 119 # Examples: 268435456 (256MB), 536870912 (512MB)
118 120 # 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
119 121 # Dynamic formula 1024 * 1024 * 256 == 256MBs
120 122 memory_max_usage = 0
121 123
122 124 # How often in seconds to check for memory usage for each gunicorn worker
123 125 memory_usage_check_interval = 60
124 126
125 127 # Threshold value for which we don't recycle worker if GarbageCollection
126 128 # frees up enough resources. Before each restart, we try to run GC on worker
127 129 # in case we get enough free memory after that; restart will not happen.
128 130 memory_usage_recovery_threshold = 0.8
129 131
130 132
131 133 @dataclasses.dataclass
132 134 class MemoryCheckConfig:
133 135 max_usage: int
134 136 check_interval: int
135 137 recovery_threshold: float
136 138
137 139
138 140 def _get_process_rss(pid=None):
139 try:
141 with contextlib.suppress(Exception):
140 142 import psutil
143
141 144 if pid:
142 145 proc = psutil.Process(pid)
143 146 else:
144 147 proc = psutil.Process()
145 148 return proc.memory_info().rss
146 except Exception:
149
147 150 return None
148 151
149 152
150 153 def _get_config(ini_path):
151 154 import configparser
152 155
153 try:
156 with contextlib.suppress(Exception):
154 157 config = configparser.RawConfigParser()
155 158 config.read(ini_path)
156 159 return config
157 except Exception:
160
158 161 return None
159 162
160 163
161 164 def get_memory_usage_params(config=None):
162 165 # memory spec defaults
163 166 _memory_max_usage = memory_max_usage
164 167 _memory_usage_check_interval = memory_usage_check_interval
165 168 _memory_usage_recovery_threshold = memory_usage_recovery_threshold
166 169
167 170 if config:
168 171 ini_path = os.path.abspath(config)
169 172 conf = _get_config(ini_path)
170 173
171 section = 'server:main'
174 section = "server:main"
172 175 if conf and conf.has_section(section):
176 if conf.has_option(section, "memory_max_usage"):
177 _memory_max_usage = conf.getint(section, "memory_max_usage")
173 178
174 if conf.has_option(section, 'memory_max_usage'):
175 _memory_max_usage = conf.getint(section, 'memory_max_usage')
176
177 if conf.has_option(section, 'memory_usage_check_interval'):
178 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
179 if conf.has_option(section, "memory_usage_check_interval"):
180 _memory_usage_check_interval = conf.getint(section, "memory_usage_check_interval")
179 181
180 if conf.has_option(section, 'memory_usage_recovery_threshold'):
181 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
182 if conf.has_option(section, "memory_usage_recovery_threshold"):
183 _memory_usage_recovery_threshold = conf.getfloat(section, "memory_usage_recovery_threshold")
182 184
183 _memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
184 or _memory_max_usage)
185 _memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
186 or _memory_usage_check_interval)
187 _memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
188 or _memory_usage_recovery_threshold)
185 _memory_max_usage = int(os.environ.get("RC_GUNICORN_MEMORY_MAX_USAGE", "") or _memory_max_usage)
186 _memory_usage_check_interval = int(
187 os.environ.get("RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL", "") or _memory_usage_check_interval
188 )
189 _memory_usage_recovery_threshold = float(
190 os.environ.get("RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD", "") or _memory_usage_recovery_threshold
191 )
189 192
190 193 return MemoryCheckConfig(_memory_max_usage, _memory_usage_check_interval, _memory_usage_recovery_threshold)
191 194
192 195
193 196 def _time_with_offset(check_interval):
194 197 return time.time() - random.randint(0, check_interval/2.0)
195 198
196 199
197 200 def pre_fork(server, worker):
198 201 pass
199 202
200 203
201 204 def post_fork(server, worker):
202
203 205 memory_conf = get_memory_usage_params()
204 206 _memory_max_usage = memory_conf.max_usage
205 207 _memory_usage_check_interval = memory_conf.check_interval
206 208 _memory_usage_recovery_threshold = memory_conf.recovery_threshold
207 209
208 worker._memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
209 or _memory_max_usage)
210 worker._memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
211 or _memory_usage_check_interval)
212 worker._memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
213 or _memory_usage_recovery_threshold)
210 worker._memory_max_usage = int(os.environ.get("RC_GUNICORN_MEMORY_MAX_USAGE", "") or _memory_max_usage)
211 worker._memory_usage_check_interval = int(
212 os.environ.get("RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL", "") or _memory_usage_check_interval
213 )
214 worker._memory_usage_recovery_threshold = float(
215 os.environ.get("RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD", "") or _memory_usage_recovery_threshold
216 )
214 217
215 218 # register memory last check time, with some random offset so we don't recycle all
216 219 # at once
217 220 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
218 221
219 222 if _memory_max_usage:
220 server.log.info("pid=[%-10s] WORKER spawned with max memory set at %s", worker.pid,
221 _format_data_size(_memory_max_usage))
223 server.log.info(
224 "pid=[%-10s] WORKER spawned with max memory set at %s", worker.pid, _format_data_size(_memory_max_usage)
225 )
222 226 else:
223 227 server.log.info("pid=[%-10s] WORKER spawned", worker.pid)
224 228
225 229
226 230 def pre_exec(server):
227 231 server.log.info("Forked child, re-executing.")
228 232
229 233
230 234 def on_starting(server):
231 server_lbl = '{} {}'.format(server.proc_name, server.address)
235 server_lbl = "{} {}".format(server.proc_name, server.address)
232 236 server.log.info("Server %s is starting.", server_lbl)
233 server.log.info('Config:')
237 server.log.info("Config:")
234 238 server.log.info(f"\n{server.cfg}")
235 239 server.log.info(get_memory_usage_params())
236 240
237 241
238 242 def when_ready(server):
239 243 server.log.info("Server %s is ready. Spawning workers", server)
240 244
241 245
242 246 def on_reload(server):
243 247 pass
244 248
245 249
246 250 def _format_data_size(size, unit="B", precision=1, binary=True):
247 251 """Format a number using SI units (kilo, mega, etc.).
248 252
249 253 ``size``: The number as a float or int.
250 254
251 255 ``unit``: The unit name in plural form. Examples: "bytes", "B".
252 256
253 257 ``precision``: How many digits to the right of the decimal point. Default
254 258 is 1. 0 suppresses the decimal point.
255 259
256 260 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
257 261 If true, use base-2 binary prefixes (kibi = Ki = 1024).
258 262
259 263 ``full_name``: If false (default), use the prefix abbreviation ("k" or
260 264 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
261 265 use abbreviation ("k" or "Ki").
262 266
263 267 """
264 268
265 269 if not binary:
266 270 base = 1000
267 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
271 multiples = ("", "k", "M", "G", "T", "P", "E", "Z", "Y")
268 272 else:
269 273 base = 1024
270 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
274 multiples = ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi")
271 275
272 276 sign = ""
273 277 if size > 0:
274 278 m = int(math.log(size, base))
275 279 elif size < 0:
276 280 sign = "-"
277 281 size = -size
278 282 m = int(math.log(size, base))
279 283 else:
280 284 m = 0
281 285 if m > 8:
282 286 m = 8
283 287
284 288 if m == 0:
285 precision = '%.0f'
289 precision = "%.0f"
286 290 else:
287 precision = '%%.%df' % precision
291 precision = "%%.%df" % precision
288 292
289 293 size = precision % (size / math.pow(base, m))
290 294
291 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
295 return "%s%s %s%s" % (sign, size.strip(), multiples[m], unit)
292 296
293 297
294 298 def _check_memory_usage(worker):
295 299 _memory_max_usage = worker._memory_max_usage
296 300 if not _memory_max_usage:
297 301 return
298 302
299 303 _memory_usage_check_interval = worker._memory_usage_check_interval
300 304 _memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
301 305
302 306 elapsed = time.time() - worker._last_memory_check_time
303 307 if elapsed > _memory_usage_check_interval:
304 308 mem_usage = _get_process_rss()
305 309 if mem_usage and mem_usage > _memory_max_usage:
306 310 worker.log.info(
307 "memory usage %s > %s, forcing gc",
308 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
311 "memory usage %s > %s, forcing gc", _format_data_size(mem_usage), _format_data_size(_memory_max_usage)
312 )
309 313 # Try to clean it up by forcing a full collection.
310 314 gc.collect()
311 315 mem_usage = _get_process_rss()
312 316 if mem_usage > _memory_usage_recovery_threshold:
313 317 # Didn't clean up enough, we'll have to terminate.
314 318 worker.log.warning(
315 319 "memory usage %s > %s after gc, quitting",
316 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
320 _format_data_size(mem_usage),
321 _format_data_size(_memory_max_usage),
322 )
317 323 # This will cause worker to auto-restart itself
318 324 worker.alive = False
319 325 worker._last_memory_check_time = time.time()
320 326
321 327
322 328 def worker_int(worker):
323 329 worker.log.info("pid=[%-10s] worker received INT or QUIT signal", worker.pid)
324 330
325 331 # get traceback info, when a worker crashes
326 332 def get_thread_id(t_id):
327 333 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
328 334 return id2name.get(t_id, "unknown_thread_id")
329 335
330 336 code = []
331 337 for thread_id, stack in sys._current_frames().items(): # noqa
332 code.append(
333 "\n# Thread: %s(%d)" % (get_thread_id(thread_id), thread_id))
338 code.append("\n# Thread: %s(%d)" % (get_thread_id(thread_id), thread_id))
334 339 for fname, lineno, name, line in traceback.extract_stack(stack):
335 340 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
336 341 if line:
337 342 code.append(" %s" % (line.strip()))
338 343 worker.log.debug("\n".join(code))
339 344
340 345
341 346 def worker_abort(worker):
342 347 worker.log.info("pid=[%-10s] worker received SIGABRT signal", worker.pid)
343 348
344 349
345 350 def worker_exit(server, worker):
346 351 worker.log.info("pid=[%-10s] worker exit", worker.pid)
347 352
348 353
349 354 def child_exit(server, worker):
350 355 worker.log.info("pid=[%-10s] worker child exit", worker.pid)
351 356
352 357
353 358 def pre_request(worker, req):
354 359 worker.start_time = time.time()
355 worker.log.debug(
356 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
360 worker.log.debug("GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
357 361
358 362
359 363 def post_request(worker, req, environ, resp):
360 364 total_time = time.time() - worker.start_time
361 365 # Gunicorn sometimes has problems with reading the status_code
362 status_code = getattr(resp, 'status_code', '')
366 status_code = getattr(resp, "status_code", "")
363 367 worker.log.debug(
364 368 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
365 worker.nr, req.method, req.path, status_code, total_time)
369 worker.nr,
370 req.method,
371 req.path,
372 status_code,
373 total_time,
374 )
366 375 _check_memory_usage(worker)
367 376
368 377
369 378 def _filter_proxy(ip):
370 379 """
371 380 Passed in IP addresses in HEADERS can be in a special format of multiple
372 381 ips. Those comma separated IPs are passed from various proxies in the
373 382 chain of request processing. The left-most being the original client.
374 383 We only care about the first IP which came from the org. client.
375 384
376 385 :param ip: ip string from headers
377 386 """
378 if ',' in ip:
379 _ips = ip.split(',')
387 if "," in ip:
388 _ips = ip.split(",")
380 389 _first_ip = _ips[0].strip()
381 390 return _first_ip
382 391 return ip
383 392
384 393
385 394 def _filter_port(ip):
386 395 """
387 396 Removes a port from ip, there are 4 main cases to handle here.
388 397 - ipv4 eg. 127.0.0.1
389 398 - ipv6 eg. ::1
390 399 - ipv4+port eg. 127.0.0.1:8080
391 400 - ipv6+port eg. [::1]:8080
392 401
393 402 :param ip:
394 403 """
404
395 405 def is_ipv6(ip_addr):
396 if hasattr(socket, 'inet_pton'):
406 if hasattr(socket, "inet_pton"):
397 407 try:
398 408 socket.inet_pton(socket.AF_INET6, ip_addr)
399 409 except socket.error:
400 410 return False
401 411 else:
402 412 return False
403 413 return True
404 414
405 if ':' not in ip: # must be ipv4 pure ip
415 if ":" not in ip: # must be ipv4 pure ip
406 416 return ip
407 417
408 if '[' in ip and ']' in ip: # ipv6 with port
409 return ip.split(']')[0][1:].lower()
418 if "[" in ip and "]" in ip: # ipv6 with port
419 return ip.split("]")[0][1:].lower()
410 420
411 421 # must be ipv6 or ipv4 with port
412 422 if is_ipv6(ip):
413 423 return ip
414 424 else:
415 ip, _port = ip.split(':')[:2] # means ipv4+port
425 ip, _port = ip.split(":")[:2] # means ipv4+port
416 426 return ip
417 427
418 428
419 429 def get_ip_addr(environ):
420 proxy_key = 'HTTP_X_REAL_IP'
421 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
422 def_key = 'REMOTE_ADDR'
430 proxy_key = "HTTP_X_REAL_IP"
431 proxy_key2 = "HTTP_X_FORWARDED_FOR"
432 def_key = "REMOTE_ADDR"
423 433
424 434 def _filters(x):
425 435 return _filter_port(_filter_proxy(x))
426 436
427 437 ip = environ.get(proxy_key)
428 438 if ip:
429 439 return _filters(ip)
430 440
431 441 ip = environ.get(proxy_key2)
432 442 if ip:
433 443 return _filters(ip)
434 444
435 ip = environ.get(def_key, '0.0.0.0')
445 ip = environ.get(def_key, "0.0.0.0")
436 446 return _filters(ip)
437 447
438 448
439 449 class RhodeCodeLogger(Logger):
440 450 """
441 451 Custom Logger that allows some customization that gunicorn doesn't allow
442 452 """
443 453
444 454 datefmt = r"%Y-%m-%d %H:%M:%S"
445 455
446 456 def __init__(self, cfg):
447 457 Logger.__init__(self, cfg)
448 458
449 459 def now(self):
450 460 """ return date in RhodeCode Log format """
451 461 now = time.time()
452 462 msecs = int((now - int(now)) * 1000)
453 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
463 return time.strftime(self.datefmt, time.localtime(now)) + ".{0:03d}".format(msecs)
454 464
455 465 def atoms(self, resp, req, environ, request_time):
456 """ Gets atoms for log formatting.
457 """
466 """Gets atoms for log formatting."""
458 467 status = resp.status
459 468 if isinstance(status, str):
460 469 status = status.split(None, 1)[0]
461 470 atoms = {
462 'h': get_ip_addr(environ),
463 'l': '-',
464 'u': self._get_user(environ) or '-',
465 't': self.now(),
466 'r': "%s %s %s" % (environ['REQUEST_METHOD'],
467 environ['RAW_URI'],
468 environ["SERVER_PROTOCOL"]),
469 's': status,
470 'm': environ.get('REQUEST_METHOD'),
471 'U': environ.get('PATH_INFO'),
472 'q': environ.get('QUERY_STRING'),
473 'H': environ.get('SERVER_PROTOCOL'),
474 'b': getattr(resp, 'sent', None) is not None and str(resp.sent) or '-',
475 'B': getattr(resp, 'sent', None),
476 'f': environ.get('HTTP_REFERER', '-'),
477 'a': environ.get('HTTP_USER_AGENT', '-'),
478 'T': request_time.seconds,
479 'D': (request_time.seconds * 1000000) + request_time.microseconds,
480 'M': (request_time.seconds * 1000) + int(request_time.microseconds/1000),
481 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds),
482 'p': "<%s>" % os.getpid()
471 "h": get_ip_addr(environ),
472 "l": "-",
473 "u": self._get_user(environ) or "-",
474 "t": self.now(),
475 "r": "%s %s %s" % (environ["REQUEST_METHOD"], environ["RAW_URI"], environ["SERVER_PROTOCOL"]),
476 "s": status,
477 "m": environ.get("REQUEST_METHOD"),
478 "U": environ.get("PATH_INFO"),
479 "q": environ.get("QUERY_STRING"),
480 "H": environ.get("SERVER_PROTOCOL"),
481 "b": getattr(resp, "sent", None) is not None and str(resp.sent) or "-",
482 "B": getattr(resp, "sent", None),
483 "f": environ.get("HTTP_REFERER", "-"),
484 "a": environ.get("HTTP_USER_AGENT", "-"),
485 "T": request_time.seconds,
486 "D": (request_time.seconds * 1000000) + request_time.microseconds,
487 "M": (request_time.seconds * 1000) + int(request_time.microseconds / 1000),
488 "L": "%d.%06d" % (request_time.seconds, request_time.microseconds),
489 "p": "<%s>" % os.getpid(),
483 490 }
484 491
485 492 # add request headers
486 if hasattr(req, 'headers'):
493 if hasattr(req, "headers"):
487 494 req_headers = req.headers
488 495 else:
489 496 req_headers = req
490 497
491 498 if hasattr(req_headers, "items"):
492 499 req_headers = req_headers.items()
493 500
494 501 atoms.update({"{%s}i" % k.lower(): v for k, v in req_headers})
495 502
496 503 resp_headers = resp.headers
497 504 if hasattr(resp_headers, "items"):
498 505 resp_headers = resp_headers.items()
499 506
500 507 # add response headers
501 508 atoms.update({"{%s}o" % k.lower(): v for k, v in resp_headers})
502 509
503 510 # add environ variables
504 511 environ_variables = environ.items()
505 512 atoms.update({"{%s}e" % k.lower(): v for k, v in environ_variables})
506 513
507 514 return atoms
508 515
509 516
510 517 logger_class = RhodeCodeLogger
@@ -1,59 +1,73 b''
1 1 [build-system]
2 2 requires = ["setuptools>=61.0.0", "wheel"]
3 3 build-backend = "setuptools.build_meta"
4 4
5 5 [project]
6 6 name = "rhodecode-vcsserver"
7 7 description = "Version Control System Server for RhodeCode"
8 8 authors = [
9 9 {name = "RhodeCode GmbH", email = "support@rhodecode.com"},
10 10 ]
11 11
12 12 license = {text = "GPL V3"}
13 13 requires-python = ">=3.10"
14 14 dynamic = ["version", "readme", "dependencies", "optional-dependencies"]
15 15 classifiers = [
16 16 'Development Status :: 6 - Mature',
17 17 'Intended Audience :: Developers',
18 18 'Operating System :: OS Independent',
19 19 'Topic :: Software Development :: Version Control',
20 20 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
21 21 'Programming Language :: Python :: 3.10',
22 22 ]
23 23
24 24 [project.entry-points."paste.app_factory"]
25 25 main = "vcsserver.http_main:main"
26 26
27 27
28 28 [tool.setuptools]
29 29 packages = ["vcsserver"]
30 30
31 31 [tool.setuptools.dynamic]
32 32 readme = {file = ["README.rst"], content-type = "text/rst"}
33 33 version = {file = "vcsserver/VERSION"}
34 34 dependencies = {file = ["requirements.txt"]}
35 35 optional-dependencies.tests = {file = ["requirements_test.txt"]}
36 36
37 37 [tool.ruff]
38
38 39 select = [
39 40 # Pyflakes
40 41 "F",
41 42 # Pycodestyle
42 43 "E",
43 44 "W",
44 45 # isort
45 46 "I001"
46 47 ]
48
47 49 ignore = [
48 50 "E501", # line too long, handled by black
49 51 ]
52
50 53 # Same as Black.
51 54 line-length = 120
52 55
56 [tool.ruff.isort]
53 57
54 [tool.ruff.isort]
55 58 known-first-party = ["vcsserver"]
56 59
57 [tool.black]
58 line-length = 120
59 target-version = ['py310', 'py311']
60 [tool.ruff.format]
61
62 # Like Black, use double quotes for strings.
63 quote-style = "double"
64
65 # Like Black, indent with spaces, rather than tabs.
66 indent-style = "space"
67
68 # Like Black, respect magic trailing commas.
69 skip-magic-trailing-comma = false
70
71 # Like Black, automatically detect the appropriate line ending.
72 line-ending = "auto"
73
General Comments 0
You need to be logged in to leave comments. Login now