Патч генерируется на Python, а затем я хотел бы сохранить эти данные в файлеchanges.patch внутри Docker-контейнера, чтобы я мог впоследствии применить патч, зафиксировать изменения и т. д. Вариант использования этого заключается в том, чтобы AI-агент автоматически выбирал фрагменты и итеративно обрабатывал их в фиксации. Из-за этого я, если я что-то не пропустил, не могу использовать встроенные интерактивные команды git (в данном случае git add -e), поскольку они зависают на терминале - вот и этот обходной путь.Чтобы заполнить файл патча сгенерированными различиями, я неоднократно натыкался на Here-Documents [1][2][3], с помощью которого я сейчас пытаюсь это реализовать.
Соответствующая часть моего кода выглядит так следует:
Код: Выделить всё
extracted_data = ''.join([diff_header] + selected_hunks)
command = '/bin/bash -c "{command}"' # Need to quote command for it to actually be executed in the container
update_patch_file_command = command.format(command=f"cat > {file all_changes.patch {
+ if (javaType is ParameterizedType && Optional::class.isAssignableFrom(javaType.rawType)) {
+ // parse and wrap with optional
+ return Optional.ofNullable(parseInput(value, type, javaType.actualTypeArguments[0], environment))
+ }
+
+ when (val graphQLType = environment.graphQLSchema?.getType(type.name)) {
+ is GraphQLInputObjectType -> {
+ if (value == null) return value
+ return if ((javaType as Class).constructors.any { it.parameters.isEmpty() }) {
+ parseInputObjectWithNoArgsConstructor(javaType, graphQLType, value, environment)
+ } else {
+ parseInputObjectWithAllArgsConstructor(javaType, graphQLType, value, environment)
+ }
+ }
+ is GraphQLScalarType -> when {
+ type.name == "ID" -> parseIdInput(value, javaType)
+ javaType is Class && javaType.isPrimitive && value == null -> getPrimitiveDefault(javaType)
+ else -> value
+ }
+ is GraphQLEnumType -> when (value) {
+ is String -> (javaType as Class)
+ .getMethod("valueOf", String::class.java)
+ .invoke(null, value)
+ else -> value
+ }
+ else -> value
+ }
+ }
+ is ListType -> when {
+ value == null -> value
+ javaType is ParameterizedType && Optional::class.isAssignableFrom(javaType.rawType) -> {
+ // parse and wrap with optional
+ Optional.ofNullable(parseInput(value, type, javaType.actualTypeArguments[0], environment))
+ }
+ javaType is ParameterizedType && Collection::class.isAssignableFrom(javaType.rawType) -> {
+ val collection = (value as Collection).map {
+ parseInput(it, type.type, javaType.actualTypeArguments[0], environment)
+ }
+
+ return when {
+ List::class.isAssignableFrom(javaType.rawType) -> collection.toList()
+ Set::class.isAssignableFrom(javaType.rawType) -> collection.toSet()
+ else -> collection
+ }
+ }
+ javaType is WildcardType -> parseInput(value, type, javaType.upperBounds[0], environment)
+ else -> value
+ }
+ else -> value
+ }
+ }
+
+ private fun parseInputObjectWithAllArgsConstructor(
+ javaType: Class,
+ graphQLType: GraphQLInputObjectType,
+ value: Any?,
+ environment: DataFetchingEnvironment
+ ): Any? {
+ val fields = parseInputObjectFields(javaType, graphQLType, value, environment)
+
+ return javaType
+ .getDeclaredConstructor(*fields.map { it.first.type }.toTypedArray())
+ .newInstance(*fields.map { it.second }.toTypedArray())
}
- /**
- * A concrete scalar type is a scalar type where values always coerce to the same Java type. The ID scalar type is not concrete
- * because values can be coerced to multiple different Java types (eg. String, Long, UUID). All values of a non-concrete scalar
- * type must be converted to the target method parameter type.
- */
- private fun isConcreteScalarType(environment: DataFetchingEnvironment, type: Type, genericParameterType: JavaType): Boolean {
- return when (type) {
- is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType))
- && isConcreteScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType))
- is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) && type.name != "ID" }
- ?: false
- is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)
- else -> false
+ private fun parseInputObjectWithNoArgsConstructor(
+ javaType: Class,
+ graphQLType: GraphQLInputObjectType,
+ value: Any?,
+ environment: DataFetchingEnvironment
+ ): Any? {
+ val inputObject = javaType.getDeclaredConstructor().newInstance()
+
+ parseInputObjectFields(javaType, graphQLType, value, environment)
+ .forEach {
+ val field = it.first
+ field.isAccessible = true
+ field.set(inputObject, it.second)
+ }
+
+ return inputObject
+ }
+
+ private fun parseInputObjectFields(
+ javaType: Class,
+ graphQLType: GraphQLInputObjectType,
+ value: Any?,
+ environment: DataFetchingEnvironment
+ ): List
> {
+ return javaType.declaredFields
+ .filterNot { it.isSynthetic }
+ // TODO use an annotation specific to graphql (i.e. GraphQLIgnore?)
+ .filterNot { it.isAnnotationPresent(JsonIgnore::class.java) }
+ .map {
+ val graphQLField = graphQLType.fields.find { t -> t.definition.name == it.name }
+ ?: throw IllegalArgumentException("Could not construct input object: missing field '${it.name}' in '${graphQLType.name}' ")
+ val fieldValue = (value as Map)[graphQLField.definition.name]
+ val parsedValue = parseInput(fieldValue, graphQLField.definition.type, it.genericType, environment)
+ Pair(it, parsedValue)
+ }
+ }
+
+ private fun parseIdInput(value: Any?, javaType: JavaType) = when {
+ value !is String // if value was already coerced
+ || String::class.isAssignableFrom(javaType) -> value
+ Int::class.isAssignableFrom(javaType)
+ || Integer::class.isAssignableFrom(javaType) -> Integer.parseInt(value)
+ Long::class.isAssignableFrom(javaType)
+ || java.lang.Long::class.isAssignableFrom(javaType) -> java.lang.Long.parseLong(value)
+ UUID::class.isAssignableFrom(javaType) -> UUID.fromString(value)
+ else -> value
+ }
+
+ private fun KClass.isAssignableFrom(javaType: JavaType): Boolean {
+ return this.java.isAssignableFrom(javaType.unwrap())
+ }
+
+ private fun getPrimitiveDefault(javaType: Class): Any? {
+ return when (javaType) {
+ Boolean::class.java -> false
+ Byte::class.java -> 0
+ Char::class.java -> '\u0000'
+ Int::class.java -> 0
+ Short::class.java -> 0
+ Long::class.java -> 0L
+ Float::class.java -> 0.0f
+ Double::class.java -> 0.0
+ else -> null
}
}
EOL"
Как видно выше, перед EOL нет пробелов (они копируются непосредственно из отладчика), на что может намекать эта ошибка at.
Я пробовал еще следующее:
- : не работает, потому что мои строки многострочные
Код: Выделить всё
echo - не двойные кавычки в вызове bash: не работает, потому что тогда команда вообще не выполняется
- переход к файлу, который еще не существует
- с использованием повышенных разрешений для контейнера.run_exec
- используя >|, чтобы принудительно перезаписать файл
- использование shlex.quote: та же ошибка b"not: строка 129: предупреждение: здесь документ в строке 1 ограничен концом файла (требуется EOL')\n" . Кроме того, насколько мне известно, я считаю, что использовать его безопасно.
- используя тройник вместо кота
Я мог бы скопировать файл с помощью функции Python Docker SDK put_archive, но это кажется очень хакерским и, вероятно, плохо масштабируется. Поэтому я хотел бы найти другое решение.
РЕДАКТИРОВАТЬ 1:
Кроме того, я только что понял, что могу выполнить всю логику извлечения фрагментов в оболочке. скрипт, возможно, полностью избегает взаимодействия с docker/python.
Подробнее здесь: https://stackoverflow.com/questions/793 ... ocker-cont
Мобильная версия